--- /srv/rebuilderd/tmp/rebuilderdYD3rh2/inputs/erlang-doc_29.0~rc2+dfsg-1_all.deb +++ /srv/rebuilderd/tmp/rebuilderdYD3rh2/out/erlang-doc_29.0~rc2+dfsg-1_all.deb ├── file list │ @@ -1,3 +1,3 @@ │ -rw-r--r-- 0 0 0 4 2026-03-19 08:25:37.000000 debian-binary │ --rw-r--r-- 0 0 0 60972 2026-03-19 08:25:37.000000 control.tar.xz │ --rw-r--r-- 0 0 0 24833160 2026-03-19 08:25:37.000000 data.tar.xz │ +-rw-r--r-- 0 0 0 61012 2026-03-19 08:25:37.000000 control.tar.xz │ +-rw-r--r-- 0 0 0 24832800 2026-03-19 08:25:37.000000 data.tar.xz ├── control.tar.xz │ ├── control.tar │ │ ├── ./control │ │ │ @@ -1,13 +1,13 @@ │ │ │ Package: erlang-doc │ │ │ Source: erlang │ │ │ Version: 1:29.0~rc2+dfsg-1 │ │ │ Architecture: all │ │ │ Maintainer: Debian Erlang Packagers │ │ │ -Installed-Size: 109338 │ │ │ +Installed-Size: 109339 │ │ │ Depends: libjs-jquery, libjs-jquery-ui │ │ │ Suggests: erlang:any │ │ │ Conflicts: erlang-base:any (<< 1:13.b.4), erlang-base-hipe:any, erlang-doc-html │ │ │ Replaces: erlang-doc-html │ │ │ Provides: erlang-doc-html │ │ │ Section: doc │ │ │ Priority: optional │ │ ├── ./md5sums │ │ │ ├── ./md5sums │ │ │ │┄ Files differ │ │ │ ├── line order │ │ │ │ @@ -1314,15 +1314,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/dist/search_data-BC60C812.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/dist/search_data-063C1F50.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/dist/sidebar_items-B5E0D96A.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/mnesia.epub │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/mnesia.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/mnesia_app_a.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/mnesia_app_b.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/mnesia_app_c.html │ │ │ │ @@ -1430,15 +1430,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/dist/search_data-CB7B0CC2.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/dist/search_data-010749A4.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/dist/sidebar_items-8A386B63.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/memsup.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/notes.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/nteventlog.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/os_mon.epub │ │ │ │ usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/os_mon_app.html │ │ │ │ @@ -1822,15 +1822,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/dist/search_data-46E2864D.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/dist/search_data-569CFE08.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/dist/sidebar_items-06298762.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/notes.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/search.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/ssl.epub │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/ssl.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/ssl_app.html │ │ │ │ @@ -2048,15 +2048,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/dist/search_data-5D5AFE43.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/dist/search_data-77E739CD.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/dist/sidebar_items-DC8B8D19.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/eprof.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/erlang-el.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/erlang_mode_chapter.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/fprof.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/fprof_chapter.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/index.html ├── data.tar.xz │ ├── data.tar │ │ ├── file list │ │ │ @@ -140,15 +140,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 287 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/doc/ssh.html │ │ │ -rw-r--r-- 0 root (0) root (0) 290 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/doc/ssl.html │ │ │ -rw-r--r-- 0 root (0) root (0) 290 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/doc/stdlib.html │ │ │ -rw-r--r-- 0 root (0) root (0) 296 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/doc/syntax_tools.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/doc/system/ │ │ │ -rw-r--r-- 0 root (0) root (0) 2373 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/doc/system/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 5648 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/doc/system/404.html │ │ │ --rw-r--r-- 0 root (0) root (0) 766201 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/doc/system/Erlang System Documentation.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 766242 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/doc/system/Erlang System Documentation.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 53683 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/doc/system/applications.html │ │ │ -rw-r--r-- 0 root (0) root (0) 97463 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/doc/system/appup_cookbook.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 7982 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/ballpoint-pen.svg │ │ │ -rw-r--r-- 0 root (0) root (0) 2284 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/dist1.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 5214 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/dist2.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 5007 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/dist3.gif │ │ │ @@ -363,15 +363,15 @@ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.5/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.5/doc/examples/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1006 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.5/doc/examples/P-Record.asn │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.5/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1060 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.5/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 5998 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.5/doc/html/404.html │ │ │ -rw-r--r-- 0 root (0) root (0) 6676 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.5/doc/html/api-reference.html │ │ │ --rw-r--r-- 0 root (0) root (0) 98701 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.5/doc/html/asn1.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 98695 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.5/doc/html/asn1.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 141092 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.5/doc/html/asn1_getting_started.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9316 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.5/doc/html/asn1_introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7442 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.5/doc/html/asn1_overview.html │ │ │ -rw-r--r-- 0 root (0) root (0) 79225 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.5/doc/html/asn1_spec.html │ │ │ -rw-r--r-- 0 root (0) root (0) 35357 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.5/doc/html/asn1ct.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.5/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1340 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.5/doc/html/assets/exclusive_Win_But.gif │ │ │ @@ -409,15 +409,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 11077 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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) 409846 2026-03-19 08:25:37.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) 409852 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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) 45083 2026-03-19 08:25:37.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) 29920 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.31/doc/html/ct_ftp.html │ │ │ @@ -479,15 +479,15 @@ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-10.0/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2026-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-10.0/doc/html/beam_ssa.html │ │ │ -rw-r--r-- 0 root (0) root (0) 472386 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-10.0/doc/html/cerl_trees.html │ │ │ -rw-r--r-- 0 root (0) root (0) 90639 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-10.0/doc/html/compile.html │ │ │ --rw-r--r-- 0 root (0) root (0) 201499 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-10.0/doc/html/compiler.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 201503 2026-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-10.0/doc/html/dist/ │ │ │ -rw-r--r-- 0 root (0) root (0) 20933 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-10.0/doc/html/dist/inconsolata-latin-700-normal-S55P5GAG.woff2 │ │ │ @@ -513,15 +513,15 @@ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 978 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6010 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/assets/logo.png │ │ │ --rw-r--r-- 0 root (0) root (0) 140270 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/crypto.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 140276 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/crypto.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 357299 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/dist/ │ │ │ -rw-r--r-- 0 root (0) root (0) 20933 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.9/doc/html/dist/html-erlang-WGRVP7UZ.css │ │ │ @@ -558,15 +558,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 21770 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.1/doc/html/assets/cond_break_dialog.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 13532 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.1/doc/html/assets/function_break_dialog.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 28924 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.1/doc/html/assets/interpret.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 14414 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.1/doc/html/assets/line_break_dialog.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.1/doc/html/assets/logo.png │ │ │ -rw-r--r-- 0 root (0) root (0) 40742 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.1/doc/html/assets/monitor.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 34504 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.1/doc/html/assets/view.jpg │ │ │ --rw-r--r-- 0 root (0) root (0) 221591 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.1/doc/html/debugger.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 221594 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.1/doc/html/debugger.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 13075 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.1/doc/html/debugger.html │ │ │ -rw-r--r-- 0 root (0) root (0) 52016 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.1/doc/html/debugger_chapter.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.1/doc/html/dist/ │ │ │ -rw-r--r-- 0 root (0) root (0) 20933 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.1/doc/html/dist/handlebars.runtime-CFQAK6SD.js │ │ │ -rw-r--r-- 0 root (0) root (0) 33580 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.1/doc/html/dist/handlebars.templates-K7URE6B4.js │ │ │ -rw-r--r-- 0 root (0) root (0) 70589 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.1/doc/html/dist/html-55NP3CS6.js │ │ │ -rw-r--r-- 0 root (0) root (0) 67213 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-6.1/doc/html/dist/html-erlang-WGRVP7UZ.css │ │ │ @@ -595,15 +595,15 @@ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 921 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6022 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/html/assets/logo.png │ │ │ --rw-r--r-- 0 root (0) root (0) 68549 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/html/dialyzer.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 68545 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/html/dist/ │ │ │ -rw-r--r-- 0 root (0) root (0) 20933 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-6.0/doc/html/dist/html-erlang-WGRVP7UZ.css │ │ │ @@ -648,15 +648,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 2379 2026-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.7/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1143 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.7/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6022 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.7/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.7/doc/html/assets/logo.png │ │ │ --rw-r--r-- 0 root (0) root (0) 148454 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.7/doc/html/diameter.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 148458 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.7/doc/html/diameter_make.html │ │ │ @@ -782,15 +782,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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) 33487 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.3/doc/html/eldap.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 33491 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.3/doc/html/eldap.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 94387 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.7/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.7/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.7/doc/html/ │ │ │ @@ -819,15 +819,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 199699 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.7/doc/html/dist/search_data-FF89A9CD.js │ │ │ -rw-r--r-- 0 root (0) root (0) 16288 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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) 86641 2026-03-19 08:25:37.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) 86644 2026-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.7/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 119929 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/examples/ │ │ │ -rw-r--r-- 0 root (0) root (0) 15431 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/examples/et_demo.erl │ │ │ @@ -866,15 +866,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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) 303472 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/html/et.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 303478 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.8/doc/html/et_tutorial.html │ │ │ @@ -910,15 +910,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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) 84042 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.11/doc/html/dist/search_data-EA268783.js │ │ │ -rw-r--r-- 0 root (0) root (0) 3219 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.11/doc/html/dist/sidebar_items-78A289CD.js │ │ │ --rw-r--r-- 0 root (0) root (0) 48746 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.11/doc/html/eunit.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 48744 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/eunit-2.11/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 48868 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.3/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.3/doc/ │ │ │ @@ -944,15 +944,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.3/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.3/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.3/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.3/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.3/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 30319 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.3/doc/html/dist/search_data-F8151C71.js │ │ │ -rw-r--r-- 0 root (0) root (0) 5346 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.3/doc/html/dist/sidebar_items-E377DC22.js │ │ │ --rw-r--r-- 0 root (0) root (0) 33876 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.3/doc/html/ftp.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 33875 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.3/doc/html/ftp.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 81693 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.3/doc/html/ftp.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12850 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.3/doc/html/ftp_client.html │ │ │ -rw-r--r-- 0 root (0) root (0) 259 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.3/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7156 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.3/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 23763 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.3/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5908 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.3/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.7/ │ │ │ @@ -1115,15 +1115,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 11377 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.7/doc/html/http_uri.html │ │ │ -rw-r--r-- 0 root (0) root (0) 92083 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.7/doc/html/httpc.html │ │ │ -rw-r--r-- 0 root (0) root (0) 118167 2026-03-19 08:25:37.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-03-19 08:25:37.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) 13393 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.7/doc/html/index.html │ │ │ --rw-r--r-- 0 root (0) root (0) 158322 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.7/doc/html/inets.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 158328 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.7/doc/html/mod_security.html │ │ │ @@ -1360,15 +1360,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 57052 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-11.0/doc/html/inet.html │ │ │ -rw-r--r-- 0 root (0) root (0) 92964 2026-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-11.0/doc/html/introduction_chapter.html │ │ │ --rw-r--r-- 0 root (0) root (0) 834022 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-11.0/doc/html/kernel.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 834032 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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) 5624 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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) 213264 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/dist/search_data-111DCD2B.js │ │ │ -rw-r--r-- 0 root (0) root (0) 33651 2026-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/index.html │ │ │ --rw-r--r-- 0 root (0) root (0) 146996 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/megaco.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 147000 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/megaco_codec_mstone1.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9831 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/megaco_codec_mstone2.html │ │ │ -rw-r--r-- 0 root (0) root (0) 10081 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/megaco_codec_transform.html │ │ │ -rw-r--r-- 0 root (0) root (0) 18670 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.9/doc/html/megaco_debug.html │ │ │ @@ -1505,18 +1505,18 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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) 385897 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/dist/search_data-BC60C812.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 385897 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/dist/search_data-063C1F50.js │ │ │ -rw-r--r-- 0 root (0) root (0) 25277 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/dist/sidebar_items-B5E0D96A.js │ │ │ -rw-r--r-- 0 root (0) root (0) 263 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/index.html │ │ │ --rw-r--r-- 0 root (0) root (0) 226186 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/mnesia.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 226188 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/mnesia.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 332839 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.26/doc/html/mnesia_chap3.html │ │ │ @@ -1564,15 +1564,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 150280 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.19/doc/html/dist/search_data-CBDD946A.js │ │ │ -rw-r--r-- 0 root (0) root (0) 12965 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.19/doc/html/introduction_ug.html │ │ │ -rw-r--r-- 0 root (0) root (0) 76659 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.19/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 118420 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.19/doc/html/observer.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 118427 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.17/ │ │ │ @@ -1606,15 +1606,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 78987 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.17/doc/html/dist/search_data-8760BDA9.js │ │ │ -rw-r--r-- 0 root (0) root (0) 7467 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.17/doc/html/dist/sidebar_items-96514CC7.js │ │ │ -rw-r--r-- 0 root (0) root (0) 13859 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.17/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 60378 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.17/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 67900 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.17/doc/html/odbc.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 67902 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.17/doc/html/odbc.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 76526 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 952 2026-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/404.html │ │ │ @@ -1637,21 +1637,21 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 71471 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/dist/search_data-CB7B0CC2.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 71471 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/dist/search_data-010749A4.js │ │ │ -rw-r--r-- 0 root (0) root (0) 7929 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/dist/sidebar_items-8A386B63.js │ │ │ -rw-r--r-- 0 root (0) root (0) 263 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 31289 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/memsup.html │ │ │ -rw-r--r-- 0 root (0) root (0) 60370 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 14742 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/nteventlog.html │ │ │ --rw-r--r-- 0 root (0) root (0) 50914 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/os_mon.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 50913 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/os_mon.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 9973 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/os_mon_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 22896 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/os_sup.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5929 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.12/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.8/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.8/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.8/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 890 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.8/doc/html/.build │ │ │ @@ -1678,15 +1678,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-03-19 08:25:37.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-03-19 08:25:37.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) 58685 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.8/doc/html/dist/search_data-B2D76990.js │ │ │ -rw-r--r-- 0 root (0) root (0) 5788 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.8/doc/html/leex.html │ │ │ -rw-r--r-- 0 root (0) root (0) 44348 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.8/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 46199 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.8/doc/html/parsetools.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 46200 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 952 2026-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/html/404.html │ │ │ @@ -1711,15 +1711,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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) 156484 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/html/dist/search_data-3596DE38.js │ │ │ -rw-r--r-- 0 root (0) root (0) 17619 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/html/dist/sidebar_items-61BA3282.js │ │ │ -rw-r--r-- 0 root (0) root (0) 267 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 103066 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.21/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 104776 2026-03-19 08:25:37.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) 104769 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.1/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.1/doc/ │ │ │ @@ -1750,15 +1750,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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) 93353 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.1/doc/html/dist/search_data-F206FF02.js │ │ │ -rw-r--r-- 0 root (0) root (0) 8980 2026-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.1/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 50554 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.1/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 64277 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.1/doc/html/reltool.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 64283 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/ │ │ │ @@ -1816,15 +1816,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 9768 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/lttng.html │ │ │ -rw-r--r-- 0 root (0) root (0) 49986 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/msacc.html │ │ │ -rw-r--r-- 0 root (0) root (0) 89220 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.4/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 136019 2026-03-19 08:25:37.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) 136015 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.4/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.4/doc/ │ │ │ @@ -1865,15 +1865,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 34632 2026-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.4/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 75606 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.4/doc/html/rel.html │ │ │ -rw-r--r-- 0 root (0) root (0) 80085 2026-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.4/doc/html/relup.html │ │ │ --rw-r--r-- 0 root (0) root (0) 93656 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.4/doc/html/sasl.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 93658 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.2/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.2/doc/ │ │ │ @@ -1934,15 +1934,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.2/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.2/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 558108 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.2/doc/html/dist/search_data-9B49EF21.js │ │ │ -rw-r--r-- 0 root (0) root (0) 89930 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.2/doc/html/dist/sidebar_items-C9ED4AE3.js │ │ │ -rw-r--r-- 0 root (0) root (0) 263 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.2/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 73591 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.2/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5923 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.2/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 450845 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.2/doc/html/snmp.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 450853 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.2/doc/html/snmp.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 147483 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.2/doc/html/snmp.html │ │ │ -rw-r--r-- 0 root (0) root (0) 39983 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.2/doc/html/snmp_advanced_agent.html │ │ │ -rw-r--r-- 0 root (0) root (0) 62877 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.2/doc/html/snmp_agent_config_files.html │ │ │ -rw-r--r-- 0 root (0) root (0) 51341 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.2/doc/html/snmp_agent_funct_descr.html │ │ │ -rw-r--r-- 0 root (0) root (0) 18119 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.2/doc/html/snmp_agent_netif.html │ │ │ -rw-r--r-- 0 root (0) root (0) 66597 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.2/doc/html/snmp_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 8609 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.20.2/doc/html/snmp_app_a.html │ │ │ @@ -2026,15 +2026,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 400680 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.6/doc/html/dist/search_data-3AE4A649.js │ │ │ -rw-r--r-- 0 root (0) root (0) 48077 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.6/doc/html/dist/sidebar_items-02195CAC.js │ │ │ -rw-r--r-- 0 root (0) root (0) 33737 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.6/doc/html/hardening.html │ │ │ -rw-r--r-- 0 root (0) root (0) 259 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.6/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 14191 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.6/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 262509 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.6/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5908 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.6/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 287939 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.6/doc/html/ssh.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 287934 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.6/doc/html/ssh.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 260076 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.6/doc/html/ssh.html │ │ │ -rw-r--r-- 0 root (0) root (0) 24735 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.6/doc/html/ssh_agent.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25721 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.6/doc/html/ssh_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 44535 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.6/doc/html/ssh_client_channel.html │ │ │ -rw-r--r-- 0 root (0) root (0) 23183 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.6/doc/html/ssh_client_key_api.html │ │ │ -rw-r--r-- 0 root (0) root (0) 78519 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.6/doc/html/ssh_connection.html │ │ │ -rw-r--r-- 0 root (0) root (0) 50564 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.6/doc/html/ssh_file.html │ │ │ @@ -2086,20 +2086,20 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 508703 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/dist/search_data-46E2864D.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 508703 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/dist/search_data-569CFE08.js │ │ │ -rw-r--r-- 0 root (0) root (0) 28299 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/dist/sidebar_items-06298762.js │ │ │ -rw-r--r-- 0 root (0) root (0) 262 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 282845 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5917 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 220154 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/ssl.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 220165 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/ssl.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 324007 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/ssl.html │ │ │ -rw-r--r-- 0 root (0) root (0) 17341 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/ssl_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12862 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/ssl_crl_cache.html │ │ │ -rw-r--r-- 0 root (0) root (0) 21696 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/ssl_crl_cache_api.html │ │ │ -rw-r--r-- 0 root (0) root (0) 39362 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/ssl_distribution.html │ │ │ -rw-r--r-- 0 root (0) root (0) 14204 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/ssl_protocol.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25835 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.5.4/doc/html/ssl_session_cache_api.html │ │ │ @@ -2206,15 +2206,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5926 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 110522 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/slave.html │ │ │ -rw-r--r-- 0 root (0) root (0) 503061 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/sofs.html │ │ │ --rw-r--r-- 0 root (0) root (0) 1739157 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/stdlib.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 1739131 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/string.html │ │ │ -rw-r--r-- 0 root (0) root (0) 100430 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/terminal_interface.html │ │ │ -rw-r--r-- 0 root (0) root (0) 81036 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-8.0/doc/html/timer.html │ │ │ @@ -2298,15 +2298,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 30859 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.3/doc/html/dist/search_data-6DFDE4BA.js │ │ │ -rw-r--r-- 0 root (0) root (0) 3329 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.3/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 22211 2026-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.3/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 31524 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.3/doc/html/tftp.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 31523 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/examples/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1447 2026-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/ │ │ │ @@ -2335,29 +2335,29 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5624 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/dist/lato-latin-ext-300-normal-VPGGJKJL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5472 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/dist/lato-latin-ext-400-normal-N27NCBWW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5368 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/dist/lato-latin-ext-700-normal-Q2L5DVMW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 312168 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/dist/search_data-5D5AFE43.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 312168 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/dist/search_data-77E739CD.js │ │ │ -rw-r--r-- 0 root (0) root (0) 36276 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/dist/sidebar_items-DC8B8D19.js │ │ │ -rw-r--r-- 0 root (0) root (0) 42516 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/eprof.html │ │ │ -rw-r--r-- 0 root (0) root (0) 28453 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/erlang-el.html │ │ │ -rw-r--r-- 0 root (0) root (0) 18980 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/erlang_mode_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 131717 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/fprof.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12990 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/fprof_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 261 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/make.html │ │ │ -rw-r--r-- 0 root (0) root (0) 114886 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/tags.html │ │ │ --rw-r--r-- 0 root (0) root (0) 241721 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/tools.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 241727 2026-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.2/doc/html/tprof.html │ │ │ -rw-r--r-- 0 root (0) root (0) 188693 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/examples/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/examples/demo/ │ │ │ @@ -2450,15 +2450,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 1672227 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/html/dist/search_data-D5C73E15.js │ │ │ -rw-r--r-- 0 root (0) root (0) 578947 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 69236 2026-03-19 08:25:37.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-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 1609519 2026-03-19 08:25:37.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.6/doc/html/wx.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 1609520 2026-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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-03-19 08:25:37.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,98 +1,98 @@ │ │ │ │ -Zip file size: 766201 bytes, number of entries: 96 │ │ │ │ -?rw-r--r-- 6.1 unx 20 bx stor 26-Mar-19 09:42 mimetype │ │ │ │ -?rw-r--r-- 6.1 unx 14643 bx defN 26-Mar-19 09:42 OEBPS/vulnerabilities.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 17922 bx defN 26-Mar-19 09:42 OEBPS/versions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 4673 bx defN 26-Mar-19 09:42 OEBPS/upgrade.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 54451 bx defN 26-Mar-19 09:42 OEBPS/typespec.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2166 bx defN 26-Mar-19 09:42 OEBPS/tutorial.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 764 bx defN 26-Mar-19 09:42 OEBPS/title.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 46256 bx defN 26-Mar-19 09:42 OEBPS/tablesdatabases.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 12434 bx defN 26-Mar-19 09:42 OEBPS/system_principles.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 7485 bx defN 26-Mar-19 09:42 OEBPS/system_limits.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 63476 bx defN 26-Mar-19 09:42 OEBPS/sup_princ.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 254163 bx defN 26-Mar-19 09:42 OEBPS/statem.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 111724 bx defN 26-Mar-19 09:42 OEBPS/spec_proc.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 251369 bx defN 26-Mar-19 09:42 OEBPS/seq_prog.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 15505 bx defN 26-Mar-19 09:42 OEBPS/sbom.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 70943 bx defN 26-Mar-19 09:42 OEBPS/robustness.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 20877 bx defN 26-Mar-19 09:42 OEBPS/release_structure.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 59980 bx defN 26-Mar-19 09:42 OEBPS/release_handling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 4596 bx defN 26-Mar-19 09:42 OEBPS/reference_manual.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 19455 bx defN 26-Mar-19 09:42 OEBPS/ref_man_records.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 55171 bx defN 26-Mar-19 09:42 OEBPS/ref_man_processes.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 14477 bx defN 26-Mar-19 09:42 OEBPS/ref_man_functions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 49542 bx defN 26-Mar-19 09:42 OEBPS/records_macros.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2357 bx defN 26-Mar-19 09:42 OEBPS/readme.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 785 bx defN 26-Mar-19 09:42 OEBPS/programming_examples.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 40216 bx defN 26-Mar-19 09:42 OEBPS/prog_ex_records.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 15201 bx defN 26-Mar-19 09:42 OEBPS/profiling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 8501 bx defN 26-Mar-19 09:42 OEBPS/ports.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 3852 bx defN 26-Mar-19 09:42 OEBPS/patterns.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 13419 bx defN 26-Mar-19 09:42 OEBPS/overview.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 8959 bx defN 26-Mar-19 09:42 OEBPS/otp-patch-apply.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 10044 bx defN 26-Mar-19 09:42 OEBPS/opaques.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 15632 bx defN 26-Mar-19 09:42 OEBPS/nominals.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 14148 bx defN 26-Mar-19 09:42 OEBPS/nif.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 6777 bx defN 26-Mar-19 09:42 OEBPS/nav.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 25850 bx defN 26-Mar-19 09:42 OEBPS/modules.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 7012 bx defN 26-Mar-19 09:42 OEBPS/misc.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 5477 bx defN 26-Mar-19 09:42 OEBPS/memory.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 53374 bx defN 26-Mar-19 09:42 OEBPS/maps.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 39594 bx defN 26-Mar-19 09:42 OEBPS/macros.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 31439 bx defN 26-Mar-19 09:42 OEBPS/listhandling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 53792 bx defN 26-Mar-19 09:42 OEBPS/list_comprehensions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2211 bx defN 26-Mar-19 09:42 OEBPS/installation_guide.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 56033 bx defN 26-Mar-19 09:42 OEBPS/install.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 28229 bx defN 26-Mar-19 09:42 OEBPS/install-win32.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 35715 bx defN 26-Mar-19 09:42 OEBPS/install-cross.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 21439 bx defN 26-Mar-19 09:42 OEBPS/included_applications.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 3222 bx defN 26-Mar-19 09:42 OEBPS/howto_debug.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2353 bx defN 26-Mar-19 09:42 OEBPS/getting_started.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 31341 bx defN 26-Mar-19 09:42 OEBPS/gen_server_concepts.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 119135 bx defN 26-Mar-19 09:42 OEBPS/funs.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 8745 bx defN 26-Mar-19 09:42 OEBPS/features.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 268539 bx defN 26-Mar-19 09:42 OEBPS/expressions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2365 bx defN 26-Mar-19 09:42 OEBPS/example.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 26770 bx defN 26-Mar-19 09:42 OEBPS/events.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 16634 bx defN 26-Mar-19 09:42 OEBPS/errors.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 13609 bx defN 26-Mar-19 09:42 OEBPS/error_logging.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 42616 bx defN 26-Mar-19 09:42 OEBPS/erl_interface.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 18220 bx defN 26-Mar-19 09:42 OEBPS/embedded.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2085 bx defN 26-Mar-19 09:42 OEBPS/efficiency_guide.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 46683 bx defN 26-Mar-19 09:42 OEBPS/eff_guide_processes.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 21209 bx defN 26-Mar-19 09:42 OEBPS/eff_guide_functions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 9338 bx defN 26-Mar-19 09:42 OEBPS/drivers.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 47711 bx defN 26-Mar-19 09:42 OEBPS/documentation.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 14892 bx defN 26-Mar-19 09:42 OEBPS/distributed_applications.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 24450 bx defN 26-Mar-19 09:42 OEBPS/distributed.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 14562 bx defN 26-Mar-19 09:42 OEBPS/dist/epub-erlang-ESPT6BQV.css │ │ │ │ -?rw-r--r-- 6.1 unx 499 bx defN 26-Mar-19 09:42 OEBPS/dist/epub-LSJCIYTM.js │ │ │ │ -?rw-r--r-- 6.1 unx 36780 bx defN 26-Mar-19 09:42 OEBPS/design_principles.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 15003 bx defN 26-Mar-19 09:42 OEBPS/debugging.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 78743 bx defN 26-Mar-19 09:42 OEBPS/data_types.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 115067 bx defN 26-Mar-19 09:42 OEBPS/create_target.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 13870 bx defN 26-Mar-19 09:42 OEBPS/content.opf │ │ │ │ -?rw-r--r-- 6.1 unx 130039 bx defN 26-Mar-19 09:42 OEBPS/conc_prog.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 35522 bx defN 26-Mar-19 09:42 OEBPS/commoncaveats.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 11935 bx defN 26-Mar-19 09:42 OEBPS/code_loading.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 803 bx defN 26-Mar-19 09:42 OEBPS/cnode.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 5150 bx defN 26-Mar-19 09:42 OEBPS/character_set.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 40799 bx defN 26-Mar-19 09:42 OEBPS/c_portdriver.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 35602 bx defN 26-Mar-19 09:42 OEBPS/c_port.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 34819 bx defN 26-Mar-19 09:42 OEBPS/bit_syntax.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 53327 bx defN 26-Mar-19 09:42 OEBPS/binaryhandling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 7606 bx defN 26-Mar-19 09:42 OEBPS/benchmarking.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 88568 bx defN 26-Mar-19 09:42 OEBPS/assets/prio-msg-recv.png │ │ │ │ -?rw-r--r-- 6.1 unx 5837 bx defN 26-Mar-19 09:42 OEBPS/assets/logo.png │ │ │ │ -?rw-r--r-- 6.1 unx 5837 bx defN 26-Mar-19 09:42 OEBPS/assets/erlang-logo.png │ │ │ │ -?rw-r--r-- 6.1 unx 7044 bx stor 26-Mar-19 09:42 OEBPS/assets/dist5.gif │ │ │ │ -?rw-r--r-- 6.1 unx 2939 bx stor 26-Mar-19 09:42 OEBPS/assets/dist4.gif │ │ │ │ -?rw-r--r-- 6.1 unx 5007 bx stor 26-Mar-19 09:42 OEBPS/assets/dist3.gif │ │ │ │ -?rw-r--r-- 6.1 unx 5214 bx stor 26-Mar-19 09:42 OEBPS/assets/dist2.gif │ │ │ │ -?rw-r--r-- 6.1 unx 2284 bx stor 26-Mar-19 09:42 OEBPS/assets/dist1.gif │ │ │ │ -?rw-r--r-- 6.1 unx 7982 bx stor 26-Mar-19 09:42 OEBPS/assets/ballpoint-pen.svg │ │ │ │ -?rw-r--r-- 6.1 unx 91727 bx defN 26-Mar-19 09:42 OEBPS/appup_cookbook.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 47929 bx defN 26-Mar-19 09:42 OEBPS/applications.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 252 bx defN 26-Mar-19 09:42 META-INF/container.xml │ │ │ │ -?rw-r--r-- 6.1 unx 162 bx defN 26-Mar-19 09:42 META-INF/com.apple.ibooks.display-options.xml │ │ │ │ -96 files, 3272973 bytes uncompressed, 749397 bytes compressed: 77.1% │ │ │ │ +Zip file size: 766242 bytes, number of entries: 96 │ │ │ │ +?rw-r--r-- 6.1 unx 20 bx stor 26-Mar-19 15:36 mimetype │ │ │ │ +?rw-r--r-- 6.1 unx 14643 bx defN 26-Mar-19 15:36 OEBPS/vulnerabilities.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 17922 bx defN 26-Mar-19 15:36 OEBPS/versions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 4673 bx defN 26-Mar-19 15:36 OEBPS/upgrade.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 54451 bx defN 26-Mar-19 15:36 OEBPS/typespec.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2166 bx defN 26-Mar-19 15:36 OEBPS/tutorial.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 764 bx defN 26-Mar-19 15:36 OEBPS/title.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 46256 bx defN 26-Mar-19 15:36 OEBPS/tablesdatabases.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 12434 bx defN 26-Mar-19 15:36 OEBPS/system_principles.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 7485 bx defN 26-Mar-19 15:36 OEBPS/system_limits.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 63476 bx defN 26-Mar-19 15:36 OEBPS/sup_princ.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 254163 bx defN 26-Mar-19 15:36 OEBPS/statem.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 111724 bx defN 26-Mar-19 15:36 OEBPS/spec_proc.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 251369 bx defN 26-Mar-19 15:36 OEBPS/seq_prog.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 15505 bx defN 26-Mar-19 15:36 OEBPS/sbom.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 70943 bx defN 26-Mar-19 15:36 OEBPS/robustness.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 20877 bx defN 26-Mar-19 15:36 OEBPS/release_structure.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 59980 bx defN 26-Mar-19 15:36 OEBPS/release_handling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 4596 bx defN 26-Mar-19 15:36 OEBPS/reference_manual.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 19455 bx defN 26-Mar-19 15:36 OEBPS/ref_man_records.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 55171 bx defN 26-Mar-19 15:36 OEBPS/ref_man_processes.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 14477 bx defN 26-Mar-19 15:36 OEBPS/ref_man_functions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 49542 bx defN 26-Mar-19 15:36 OEBPS/records_macros.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2357 bx defN 26-Mar-19 15:36 OEBPS/readme.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 785 bx defN 26-Mar-19 15:36 OEBPS/programming_examples.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 40216 bx defN 26-Mar-19 15:36 OEBPS/prog_ex_records.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 15201 bx defN 26-Mar-19 15:36 OEBPS/profiling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 8501 bx defN 26-Mar-19 15:36 OEBPS/ports.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 3852 bx defN 26-Mar-19 15:36 OEBPS/patterns.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 13419 bx defN 26-Mar-19 15:36 OEBPS/overview.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 8959 bx defN 26-Mar-19 15:36 OEBPS/otp-patch-apply.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 10044 bx defN 26-Mar-19 15:36 OEBPS/opaques.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 15632 bx defN 26-Mar-19 15:36 OEBPS/nominals.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 14148 bx defN 26-Mar-19 15:36 OEBPS/nif.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 6777 bx defN 26-Mar-19 15:36 OEBPS/nav.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 25850 bx defN 26-Mar-19 15:36 OEBPS/modules.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 7012 bx defN 26-Mar-19 15:36 OEBPS/misc.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 5477 bx defN 26-Mar-19 15:36 OEBPS/memory.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 53374 bx defN 26-Mar-19 15:36 OEBPS/maps.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 39594 bx defN 26-Mar-19 15:36 OEBPS/macros.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 31439 bx defN 26-Mar-19 15:36 OEBPS/listhandling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 53792 bx defN 26-Mar-19 15:36 OEBPS/list_comprehensions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2211 bx defN 26-Mar-19 15:36 OEBPS/installation_guide.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 56033 bx defN 26-Mar-19 15:36 OEBPS/install.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 28229 bx defN 26-Mar-19 15:36 OEBPS/install-win32.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 35715 bx defN 26-Mar-19 15:36 OEBPS/install-cross.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 21439 bx defN 26-Mar-19 15:36 OEBPS/included_applications.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 3222 bx defN 26-Mar-19 15:36 OEBPS/howto_debug.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2353 bx defN 26-Mar-19 15:36 OEBPS/getting_started.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 31341 bx defN 26-Mar-19 15:36 OEBPS/gen_server_concepts.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 119135 bx defN 26-Mar-19 15:36 OEBPS/funs.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 8745 bx defN 26-Mar-19 15:36 OEBPS/features.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 268539 bx defN 26-Mar-19 15:36 OEBPS/expressions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2365 bx defN 26-Mar-19 15:36 OEBPS/example.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 26770 bx defN 26-Mar-19 15:36 OEBPS/events.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 16634 bx defN 26-Mar-19 15:36 OEBPS/errors.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 13609 bx defN 26-Mar-19 15:36 OEBPS/error_logging.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 42616 bx defN 26-Mar-19 15:36 OEBPS/erl_interface.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 18220 bx defN 26-Mar-19 15:36 OEBPS/embedded.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2085 bx defN 26-Mar-19 15:36 OEBPS/efficiency_guide.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 46683 bx defN 26-Mar-19 15:36 OEBPS/eff_guide_processes.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 21209 bx defN 26-Mar-19 15:36 OEBPS/eff_guide_functions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 9338 bx defN 26-Mar-19 15:36 OEBPS/drivers.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 47711 bx defN 26-Mar-19 15:36 OEBPS/documentation.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 14892 bx defN 26-Mar-19 15:36 OEBPS/distributed_applications.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 24450 bx defN 26-Mar-19 15:36 OEBPS/distributed.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 14562 bx defN 26-Mar-19 15:36 OEBPS/dist/epub-erlang-ESPT6BQV.css │ │ │ │ +?rw-r--r-- 6.1 unx 499 bx defN 26-Mar-19 15:36 OEBPS/dist/epub-LSJCIYTM.js │ │ │ │ +?rw-r--r-- 6.1 unx 36780 bx defN 26-Mar-19 15:36 OEBPS/design_principles.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 15003 bx defN 26-Mar-19 15:36 OEBPS/debugging.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 78743 bx defN 26-Mar-19 15:36 OEBPS/data_types.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 115067 bx defN 26-Mar-19 15:36 OEBPS/create_target.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 13870 bx defN 26-Mar-19 15:36 OEBPS/content.opf │ │ │ │ +?rw-r--r-- 6.1 unx 130039 bx defN 26-Mar-19 15:36 OEBPS/conc_prog.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 35522 bx defN 26-Mar-19 15:36 OEBPS/commoncaveats.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 11935 bx defN 26-Mar-19 15:36 OEBPS/code_loading.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 803 bx defN 26-Mar-19 15:36 OEBPS/cnode.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 5150 bx defN 26-Mar-19 15:36 OEBPS/character_set.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 40799 bx defN 26-Mar-19 15:36 OEBPS/c_portdriver.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 35602 bx defN 26-Mar-19 15:36 OEBPS/c_port.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 34819 bx defN 26-Mar-19 15:36 OEBPS/bit_syntax.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 53327 bx defN 26-Mar-19 15:36 OEBPS/binaryhandling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 7606 bx defN 26-Mar-19 15:36 OEBPS/benchmarking.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 88568 bx defN 26-Mar-19 15:36 OEBPS/assets/prio-msg-recv.png │ │ │ │ +?rw-r--r-- 6.1 unx 5837 bx defN 26-Mar-19 15:36 OEBPS/assets/logo.png │ │ │ │ +?rw-r--r-- 6.1 unx 5837 bx defN 26-Mar-19 15:36 OEBPS/assets/erlang-logo.png │ │ │ │ +?rw-r--r-- 6.1 unx 7044 bx stor 26-Mar-19 15:36 OEBPS/assets/dist5.gif │ │ │ │ +?rw-r--r-- 6.1 unx 2939 bx stor 26-Mar-19 15:36 OEBPS/assets/dist4.gif │ │ │ │ +?rw-r--r-- 6.1 unx 5007 bx stor 26-Mar-19 15:36 OEBPS/assets/dist3.gif │ │ │ │ +?rw-r--r-- 6.1 unx 5214 bx stor 26-Mar-19 15:36 OEBPS/assets/dist2.gif │ │ │ │ +?rw-r--r-- 6.1 unx 2284 bx stor 26-Mar-19 15:36 OEBPS/assets/dist1.gif │ │ │ │ +?rw-r--r-- 6.1 unx 7982 bx stor 26-Mar-19 15:36 OEBPS/assets/ballpoint-pen.svg │ │ │ │ +?rw-r--r-- 6.1 unx 91727 bx defN 26-Mar-19 15:36 OEBPS/appup_cookbook.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 47929 bx defN 26-Mar-19 15:36 OEBPS/applications.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 252 bx defN 26-Mar-19 15:36 META-INF/container.xml │ │ │ │ +?rw-r--r-- 6.1 unx 162 bx defN 26-Mar-19 15:36 META-INF/com.apple.ibooks.display-options.xml │ │ │ │ +96 files, 3272973 bytes uncompressed, 749438 bytes compressed: 77.1% │ │ │ ├── zipdetails --redact --walk --utc {} │ │ │ │ @@ -1,29 +1,29 @@ │ │ │ │ │ │ │ │ 00000 LOCAL HEADER #1 04034B50 (67324752) │ │ │ │ 00004 Extract Zip Spec 0A (10) '1.0' │ │ │ │ 00005 Extract OS 00 (0) 'MS-DOS' │ │ │ │ 00006 General Purpose Flag 0000 (0) │ │ │ │ 00008 Compression Method 0000 (0) 'Stored' │ │ │ │ -0000A Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ +0000A Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 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 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -0002F Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ +0002B Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +0002F Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 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,154 +31,154 @@ │ │ │ │ │ │ │ │ 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 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -00064 CRC 375ECD24 (928959780) │ │ │ │ -00068 Compressed Size 00000D22 (3362) │ │ │ │ +00060 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +00064 CRC C1930710 (3247638288) │ │ │ │ +00068 Compressed Size 00000D23 (3363) │ │ │ │ 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 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -00098 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ +00094 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +00098 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 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 │ │ │ │ │ │ │ │ -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 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -00DDB CRC 986340FB (2556641531) │ │ │ │ -00DDF Compressed Size 000015AF (5551) │ │ │ │ -00DE3 Uncompressed Size 00004602 (17922) │ │ │ │ -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 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -00E08 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 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 │ │ │ │ - │ │ │ │ -023CA LOCAL HEADER #4 04034B50 (67324752) │ │ │ │ -023CE Extract Zip Spec 14 (20) '2.0' │ │ │ │ -023CF Extract OS 00 (0) 'MS-DOS' │ │ │ │ -023D0 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -023D2 Compression Method 0008 (8) 'Deflated' │ │ │ │ -023D4 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -023D8 CRC 911C5DC4 (2434555332) │ │ │ │ -023DC Compressed Size 000006D3 (1747) │ │ │ │ -023E0 Uncompressed Size 00001241 (4673) │ │ │ │ -023E4 Filename Length 0013 (19) │ │ │ │ -023E6 Extra Length 001C (28) │ │ │ │ -023E8 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x23E8: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -023FB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -023FD Length 0009 (9) │ │ │ │ -023FF Flags 03 (3) 'Modification Access' │ │ │ │ -02400 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -02404 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -02408 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -0240A Length 000B (11) │ │ │ │ -0240C Version 01 (1) │ │ │ │ -0240D UID Size 04 (4) │ │ │ │ -0240E UID 00000000 (0) │ │ │ │ -02412 GID Size 04 (4) │ │ │ │ -02413 GID 00000000 (0) │ │ │ │ -02417 PAYLOAD │ │ │ │ - │ │ │ │ -02AEA LOCAL HEADER #5 04034B50 (67324752) │ │ │ │ -02AEE Extract Zip Spec 14 (20) '2.0' │ │ │ │ -02AEF Extract OS 00 (0) 'MS-DOS' │ │ │ │ -02AF0 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -02AF2 Compression Method 0008 (8) 'Deflated' │ │ │ │ -02AF4 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -02AF8 CRC CE3C2FCC (3460050892) │ │ │ │ -02AFC Compressed Size 00002E6E (11886) │ │ │ │ -02B00 Uncompressed Size 0000D4B3 (54451) │ │ │ │ -02B04 Filename Length 0014 (20) │ │ │ │ -02B06 Extra Length 001C (28) │ │ │ │ -02B08 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x2B08: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -02B1C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -02B1E Length 0009 (9) │ │ │ │ -02B20 Flags 03 (3) 'Modification Access' │ │ │ │ -02B21 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -02B25 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -02B29 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -02B2B Length 000B (11) │ │ │ │ -02B2D Version 01 (1) │ │ │ │ -02B2E UID Size 04 (4) │ │ │ │ -02B2F UID 00000000 (0) │ │ │ │ -02B33 GID Size 04 (4) │ │ │ │ -02B34 GID 00000000 (0) │ │ │ │ -02B38 PAYLOAD │ │ │ │ +00DCE LOCAL HEADER #3 04034B50 (67324752) │ │ │ │ +00DD2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +00DD3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +00DD4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +00DD6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +00DD8 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +00DDC CRC 986340FB (2556641531) │ │ │ │ +00DE0 Compressed Size 000015AF (5551) │ │ │ │ +00DE4 Uncompressed Size 00004602 (17922) │ │ │ │ +00DE8 Filename Length 0014 (20) │ │ │ │ +00DEA Extra Length 001C (28) │ │ │ │ +00DEC Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xDEC: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +00E00 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +00E02 Length 0009 (9) │ │ │ │ +00E04 Flags 03 (3) 'Modification Access' │ │ │ │ +00E05 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +00E09 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +00E0D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +00E0F Length 000B (11) │ │ │ │ +00E11 Version 01 (1) │ │ │ │ +00E12 UID Size 04 (4) │ │ │ │ +00E13 UID 00000000 (0) │ │ │ │ +00E17 GID Size 04 (4) │ │ │ │ +00E18 GID 00000000 (0) │ │ │ │ +00E1C PAYLOAD │ │ │ │ + │ │ │ │ +023CB LOCAL HEADER #4 04034B50 (67324752) │ │ │ │ +023CF Extract Zip Spec 14 (20) '2.0' │ │ │ │ +023D0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +023D1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +023D3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +023D5 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +023D9 CRC 911C5DC4 (2434555332) │ │ │ │ +023DD Compressed Size 000006D3 (1747) │ │ │ │ +023E1 Uncompressed Size 00001241 (4673) │ │ │ │ +023E5 Filename Length 0013 (19) │ │ │ │ +023E7 Extra Length 001C (28) │ │ │ │ +023E9 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x23E9: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +023FC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +023FE Length 0009 (9) │ │ │ │ +02400 Flags 03 (3) 'Modification Access' │ │ │ │ +02401 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +02405 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +02409 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +0240B Length 000B (11) │ │ │ │ +0240D Version 01 (1) │ │ │ │ +0240E UID Size 04 (4) │ │ │ │ +0240F UID 00000000 (0) │ │ │ │ +02413 GID Size 04 (4) │ │ │ │ +02414 GID 00000000 (0) │ │ │ │ +02418 PAYLOAD │ │ │ │ + │ │ │ │ +02AEB LOCAL HEADER #5 04034B50 (67324752) │ │ │ │ +02AEF Extract Zip Spec 14 (20) '2.0' │ │ │ │ +02AF0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +02AF1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +02AF3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +02AF5 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +02AF9 CRC E6380C70 (3862432880) │ │ │ │ +02AFD Compressed Size 00002E6D (11885) │ │ │ │ +02B01 Uncompressed Size 0000D4B3 (54451) │ │ │ │ +02B05 Filename Length 0014 (20) │ │ │ │ +02B07 Extra Length 001C (28) │ │ │ │ +02B09 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x2B09: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +02B1D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +02B1F Length 0009 (9) │ │ │ │ +02B21 Flags 03 (3) 'Modification Access' │ │ │ │ +02B22 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +02B26 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +02B2A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +02B2C Length 000B (11) │ │ │ │ +02B2E Version 01 (1) │ │ │ │ +02B2F UID Size 04 (4) │ │ │ │ +02B30 UID 00000000 (0) │ │ │ │ +02B34 GID Size 04 (4) │ │ │ │ +02B35 GID 00000000 (0) │ │ │ │ +02B39 PAYLOAD │ │ │ │ │ │ │ │ 059A6 LOCAL HEADER #6 04034B50 (67324752) │ │ │ │ 059AA Extract Zip Spec 14 (20) '2.0' │ │ │ │ 059AB Extract OS 00 (0) 'MS-DOS' │ │ │ │ 059AC General Purpose Flag 0000 (0) │ │ │ │ [Bits 1-2] 0 'Normal Compression' │ │ │ │ 059AE Compression Method 0008 (8) 'Deflated' │ │ │ │ -059B0 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ +059B0 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ 059B4 CRC A85875F8 (2824369656) │ │ │ │ 059B8 Compressed Size 000003F0 (1008) │ │ │ │ 059BC Uncompressed Size 00000876 (2166) │ │ │ │ 059C0 Filename Length 0014 (20) │ │ │ │ 059C2 Extra Length 001C (28) │ │ │ │ 059C4 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ # │ │ │ │ # WARNING: Offset 0x59C4: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ # Zero length filename │ │ │ │ # │ │ │ │ 059D8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ 059DA Length 0009 (9) │ │ │ │ 059DC Flags 03 (3) 'Modification Access' │ │ │ │ -059DD Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -059E1 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ +059DD Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +059E1 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ 059E5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ 059E7 Length 000B (11) │ │ │ │ 059E9 Version 01 (1) │ │ │ │ 059EA UID Size 04 (4) │ │ │ │ 059EB UID 00000000 (0) │ │ │ │ 059EF GID Size 04 (4) │ │ │ │ 059F0 GID 00000000 (0) │ │ │ │ @@ -186,30 +186,30 @@ │ │ │ │ │ │ │ │ 05DE4 LOCAL HEADER #7 04034B50 (67324752) │ │ │ │ 05DE8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ 05DE9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ 05DEA General Purpose Flag 0000 (0) │ │ │ │ [Bits 1-2] 0 'Normal Compression' │ │ │ │ 05DEC Compression Method 0008 (8) 'Deflated' │ │ │ │ -05DEE Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ +05DEE Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ 05DF2 CRC F88D1943 (4169996611) │ │ │ │ 05DF6 Compressed Size 000001AD (429) │ │ │ │ 05DFA Uncompressed Size 000002FC (764) │ │ │ │ 05DFE Filename Length 0011 (17) │ │ │ │ 05E00 Extra Length 001C (28) │ │ │ │ 05E02 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ # │ │ │ │ # WARNING: Offset 0x5E02: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ # Zero length filename │ │ │ │ # │ │ │ │ 05E13 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ 05E15 Length 0009 (9) │ │ │ │ 05E17 Flags 03 (3) 'Modification Access' │ │ │ │ -05E18 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -05E1C Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ +05E18 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +05E1C Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ 05E20 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ 05E22 Length 000B (11) │ │ │ │ 05E24 Version 01 (1) │ │ │ │ 05E25 UID Size 04 (4) │ │ │ │ 05E26 UID 00000000 (0) │ │ │ │ 05E2A GID Size 04 (4) │ │ │ │ 05E2B GID 00000000 (0) │ │ │ │ @@ -217,6408 +217,6408 @@ │ │ │ │ │ │ │ │ 05FDC LOCAL HEADER #8 04034B50 (67324752) │ │ │ │ 05FE0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ 05FE1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ 05FE2 General Purpose Flag 0000 (0) │ │ │ │ [Bits 1-2] 0 'Normal Compression' │ │ │ │ 05FE4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -05FE6 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -05FEA CRC B6182455 (3055035477) │ │ │ │ -05FEE Compressed Size 000020C3 (8387) │ │ │ │ +05FE6 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +05FEA CRC E39B99C9 (3818625481) │ │ │ │ +05FEE Compressed Size 000020C9 (8393) │ │ │ │ 05FF2 Uncompressed Size 0000B4B0 (46256) │ │ │ │ 05FF6 Filename Length 001B (27) │ │ │ │ 05FF8 Extra Length 001C (28) │ │ │ │ 05FFA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ # │ │ │ │ # WARNING: Offset 0x5FFA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ # Zero length filename │ │ │ │ # │ │ │ │ 06015 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ 06017 Length 0009 (9) │ │ │ │ 06019 Flags 03 (3) 'Modification Access' │ │ │ │ -0601A Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -0601E Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ +0601A Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +0601E Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ 06022 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ 06024 Length 000B (11) │ │ │ │ 06026 Version 01 (1) │ │ │ │ 06027 UID Size 04 (4) │ │ │ │ 06028 UID 00000000 (0) │ │ │ │ 0602C GID Size 04 (4) │ │ │ │ 0602D GID 00000000 (0) │ │ │ │ 06031 PAYLOAD │ │ │ │ │ │ │ │ -080F4 LOCAL HEADER #9 04034B50 (67324752) │ │ │ │ -080F8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -080F9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -080FA General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -080FC Compression Method 0008 (8) 'Deflated' │ │ │ │ -080FE Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -08102 CRC 93C4F0D6 (2479157462) │ │ │ │ -08106 Compressed Size 00000E65 (3685) │ │ │ │ -0810A Uncompressed Size 00003092 (12434) │ │ │ │ -0810E Filename Length 001D (29) │ │ │ │ -08110 Extra Length 001C (28) │ │ │ │ -08112 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8112: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -0812F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -08131 Length 0009 (9) │ │ │ │ -08133 Flags 03 (3) 'Modification Access' │ │ │ │ -08134 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -08138 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -0813C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -0813E Length 000B (11) │ │ │ │ -08140 Version 01 (1) │ │ │ │ -08141 UID Size 04 (4) │ │ │ │ -08142 UID 00000000 (0) │ │ │ │ -08146 GID Size 04 (4) │ │ │ │ -08147 GID 00000000 (0) │ │ │ │ -0814B PAYLOAD │ │ │ │ - │ │ │ │ -08FB0 LOCAL HEADER #10 04034B50 (67324752) │ │ │ │ -08FB4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -08FB5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -08FB6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -08FB8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -08FBA Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -08FBE CRC 4F204BB6 (1327516598) │ │ │ │ -08FC2 Compressed Size 00000994 (2452) │ │ │ │ -08FC6 Uncompressed Size 00001D3D (7485) │ │ │ │ -08FCA Filename Length 0019 (25) │ │ │ │ -08FCC Extra Length 001C (28) │ │ │ │ -08FCE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8FCE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -08FE7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -08FE9 Length 0009 (9) │ │ │ │ -08FEB Flags 03 (3) 'Modification Access' │ │ │ │ -08FEC Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -08FF0 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -08FF4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -08FF6 Length 000B (11) │ │ │ │ -08FF8 Version 01 (1) │ │ │ │ -08FF9 UID Size 04 (4) │ │ │ │ -08FFA UID 00000000 (0) │ │ │ │ -08FFE GID Size 04 (4) │ │ │ │ -08FFF GID 00000000 (0) │ │ │ │ -09003 PAYLOAD │ │ │ │ - │ │ │ │ -09997 LOCAL HEADER #11 04034B50 (67324752) │ │ │ │ -0999B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -0999C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -0999D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -0999F Compression Method 0008 (8) 'Deflated' │ │ │ │ -099A1 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -099A5 CRC 40C870BB (1086877883) │ │ │ │ -099A9 Compressed Size 0000387B (14459) │ │ │ │ -099AD Uncompressed Size 0000F7F4 (63476) │ │ │ │ -099B1 Filename Length 0015 (21) │ │ │ │ -099B3 Extra Length 001C (28) │ │ │ │ -099B5 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x99B5: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -099CA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -099CC Length 0009 (9) │ │ │ │ -099CE Flags 03 (3) 'Modification Access' │ │ │ │ -099CF Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -099D3 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -099D7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -099D9 Length 000B (11) │ │ │ │ -099DB Version 01 (1) │ │ │ │ -099DC UID Size 04 (4) │ │ │ │ -099DD UID 00000000 (0) │ │ │ │ -099E1 GID Size 04 (4) │ │ │ │ -099E2 GID 00000000 (0) │ │ │ │ -099E6 PAYLOAD │ │ │ │ - │ │ │ │ -0D261 LOCAL HEADER #12 04034B50 (67324752) │ │ │ │ -0D265 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -0D266 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -0D267 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -0D269 Compression Method 0008 (8) 'Deflated' │ │ │ │ -0D26B Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -0D26F CRC 5DFD1C6A (1576868970) │ │ │ │ -0D273 Compressed Size 0000AB00 (43776) │ │ │ │ -0D277 Uncompressed Size 0003E0D3 (254163) │ │ │ │ -0D27B Filename Length 0012 (18) │ │ │ │ -0D27D Extra Length 001C (28) │ │ │ │ -0D27F Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xD27F: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -0D291 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -0D293 Length 0009 (9) │ │ │ │ -0D295 Flags 03 (3) 'Modification Access' │ │ │ │ -0D296 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -0D29A Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -0D29E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -0D2A0 Length 000B (11) │ │ │ │ -0D2A2 Version 01 (1) │ │ │ │ -0D2A3 UID Size 04 (4) │ │ │ │ -0D2A4 UID 00000000 (0) │ │ │ │ -0D2A8 GID Size 04 (4) │ │ │ │ -0D2A9 GID 00000000 (0) │ │ │ │ -0D2AD PAYLOAD │ │ │ │ - │ │ │ │ -17DAD LOCAL HEADER #13 04034B50 (67324752) │ │ │ │ -17DB1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -17DB2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -17DB3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -17DB5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -17DB7 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -17DBB CRC 228DA013 (579706899) │ │ │ │ -17DBF Compressed Size 00003B0E (15118) │ │ │ │ -17DC3 Uncompressed Size 0001B46C (111724) │ │ │ │ -17DC7 Filename Length 0015 (21) │ │ │ │ -17DC9 Extra Length 001C (28) │ │ │ │ -17DCB Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x17DCB: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -17DE0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -17DE2 Length 0009 (9) │ │ │ │ -17DE4 Flags 03 (3) 'Modification Access' │ │ │ │ -17DE5 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -17DE9 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -17DED Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -17DEF Length 000B (11) │ │ │ │ -17DF1 Version 01 (1) │ │ │ │ -17DF2 UID Size 04 (4) │ │ │ │ -17DF3 UID 00000000 (0) │ │ │ │ -17DF7 GID Size 04 (4) │ │ │ │ -17DF8 GID 00000000 (0) │ │ │ │ -17DFC PAYLOAD │ │ │ │ - │ │ │ │ -1B90A LOCAL HEADER #14 04034B50 (67324752) │ │ │ │ -1B90E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -1B90F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -1B910 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -1B912 Compression Method 0008 (8) 'Deflated' │ │ │ │ -1B914 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -1B918 CRC B8D3D7C9 (3100891081) │ │ │ │ -1B91C Compressed Size 0000919D (37277) │ │ │ │ -1B920 Uncompressed Size 0003D5E9 (251369) │ │ │ │ -1B924 Filename Length 0014 (20) │ │ │ │ -1B926 Extra Length 001C (28) │ │ │ │ -1B928 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x1B928: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -1B93C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -1B93E Length 0009 (9) │ │ │ │ -1B940 Flags 03 (3) 'Modification Access' │ │ │ │ -1B941 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -1B945 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -1B949 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -1B94B Length 000B (11) │ │ │ │ -1B94D Version 01 (1) │ │ │ │ -1B94E UID Size 04 (4) │ │ │ │ -1B94F UID 00000000 (0) │ │ │ │ -1B953 GID Size 04 (4) │ │ │ │ -1B954 GID 00000000 (0) │ │ │ │ -1B958 PAYLOAD │ │ │ │ - │ │ │ │ -24AF5 LOCAL HEADER #15 04034B50 (67324752) │ │ │ │ -24AF9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -24AFA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -24AFB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -24AFD Compression Method 0008 (8) 'Deflated' │ │ │ │ -24AFF Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -24B03 CRC 9D665911 (2640730385) │ │ │ │ -24B07 Compressed Size 00001219 (4633) │ │ │ │ -24B0B Uncompressed Size 00003C91 (15505) │ │ │ │ -24B0F Filename Length 0010 (16) │ │ │ │ -24B11 Extra Length 001C (28) │ │ │ │ -24B13 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x24B13: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -24B23 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -24B25 Length 0009 (9) │ │ │ │ -24B27 Flags 03 (3) 'Modification Access' │ │ │ │ -24B28 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -24B2C Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -24B30 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -24B32 Length 000B (11) │ │ │ │ -24B34 Version 01 (1) │ │ │ │ -24B35 UID Size 04 (4) │ │ │ │ -24B36 UID 00000000 (0) │ │ │ │ -24B3A GID Size 04 (4) │ │ │ │ -24B3B GID 00000000 (0) │ │ │ │ -24B3F PAYLOAD │ │ │ │ - │ │ │ │ -25D58 LOCAL HEADER #16 04034B50 (67324752) │ │ │ │ -25D5C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -25D5D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -25D5E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -25D60 Compression Method 0008 (8) 'Deflated' │ │ │ │ -25D62 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -25D66 CRC 8B0053E7 (2332054503) │ │ │ │ -25D6A Compressed Size 00002A5F (10847) │ │ │ │ -25D6E Uncompressed Size 0001151F (70943) │ │ │ │ -25D72 Filename Length 0016 (22) │ │ │ │ -25D74 Extra Length 001C (28) │ │ │ │ -25D76 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x25D76: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -25D8C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -25D8E Length 0009 (9) │ │ │ │ -25D90 Flags 03 (3) 'Modification Access' │ │ │ │ -25D91 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -25D95 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -25D99 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -25D9B Length 000B (11) │ │ │ │ -25D9D Version 01 (1) │ │ │ │ -25D9E UID Size 04 (4) │ │ │ │ -25D9F UID 00000000 (0) │ │ │ │ -25DA3 GID Size 04 (4) │ │ │ │ -25DA4 GID 00000000 (0) │ │ │ │ -25DA8 PAYLOAD │ │ │ │ - │ │ │ │ -28807 LOCAL HEADER #17 04034B50 (67324752) │ │ │ │ -2880B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -2880C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -2880D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -2880F Compression Method 0008 (8) 'Deflated' │ │ │ │ -28811 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -28815 CRC 103DEDE1 (272494049) │ │ │ │ -28819 Compressed Size 000014D8 (5336) │ │ │ │ -2881D Uncompressed Size 0000518D (20877) │ │ │ │ -28821 Filename Length 001D (29) │ │ │ │ -28823 Extra Length 001C (28) │ │ │ │ -28825 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x28825: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -28842 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -28844 Length 0009 (9) │ │ │ │ -28846 Flags 03 (3) 'Modification Access' │ │ │ │ -28847 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -2884B Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -2884F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -28851 Length 000B (11) │ │ │ │ -28853 Version 01 (1) │ │ │ │ -28854 UID Size 04 (4) │ │ │ │ -28855 UID 00000000 (0) │ │ │ │ -28859 GID Size 04 (4) │ │ │ │ -2885A GID 00000000 (0) │ │ │ │ -2885E PAYLOAD │ │ │ │ - │ │ │ │ -29D36 LOCAL HEADER #18 04034B50 (67324752) │ │ │ │ -29D3A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -29D3B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -29D3C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -29D3E Compression Method 0008 (8) 'Deflated' │ │ │ │ -29D40 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -29D44 CRC C2725354 (3262272340) │ │ │ │ -29D48 Compressed Size 0000380E (14350) │ │ │ │ -29D4C Uncompressed Size 0000EA4C (59980) │ │ │ │ -29D50 Filename Length 001C (28) │ │ │ │ -29D52 Extra Length 001C (28) │ │ │ │ -29D54 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x29D54: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -29D70 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -29D72 Length 0009 (9) │ │ │ │ -29D74 Flags 03 (3) 'Modification Access' │ │ │ │ -29D75 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -29D79 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -29D7D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -29D7F Length 000B (11) │ │ │ │ -29D81 Version 01 (1) │ │ │ │ -29D82 UID Size 04 (4) │ │ │ │ -29D83 UID 00000000 (0) │ │ │ │ -29D87 GID Size 04 (4) │ │ │ │ -29D88 GID 00000000 (0) │ │ │ │ -29D8C PAYLOAD │ │ │ │ - │ │ │ │ -2D59A LOCAL HEADER #19 04034B50 (67324752) │ │ │ │ -2D59E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -2D59F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -2D5A0 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -2D5A2 Compression Method 0008 (8) 'Deflated' │ │ │ │ -2D5A4 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -2D5A8 CRC 2E09C12B (772391211) │ │ │ │ -2D5AC Compressed Size 000006A2 (1698) │ │ │ │ -2D5B0 Uncompressed Size 000011F4 (4596) │ │ │ │ -2D5B4 Filename Length 001C (28) │ │ │ │ -2D5B6 Extra Length 001C (28) │ │ │ │ -2D5B8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x2D5B8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -2D5D4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -2D5D6 Length 0009 (9) │ │ │ │ -2D5D8 Flags 03 (3) 'Modification Access' │ │ │ │ -2D5D9 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -2D5DD Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -2D5E1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -2D5E3 Length 000B (11) │ │ │ │ -2D5E5 Version 01 (1) │ │ │ │ -2D5E6 UID Size 04 (4) │ │ │ │ -2D5E7 UID 00000000 (0) │ │ │ │ -2D5EB GID Size 04 (4) │ │ │ │ -2D5EC GID 00000000 (0) │ │ │ │ -2D5F0 PAYLOAD │ │ │ │ - │ │ │ │ -2DC92 LOCAL HEADER #20 04034B50 (67324752) │ │ │ │ -2DC96 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -2DC97 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -2DC98 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -2DC9A Compression Method 0008 (8) 'Deflated' │ │ │ │ -2DC9C Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -2DCA0 CRC 9FDEA958 (2682169688) │ │ │ │ -2DCA4 Compressed Size 0000107D (4221) │ │ │ │ -2DCA8 Uncompressed Size 00004BFF (19455) │ │ │ │ -2DCAC Filename Length 001B (27) │ │ │ │ -2DCAE Extra Length 001C (28) │ │ │ │ -2DCB0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x2DCB0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -2DCCB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -2DCCD Length 0009 (9) │ │ │ │ -2DCCF Flags 03 (3) 'Modification Access' │ │ │ │ -2DCD0 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -2DCD4 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -2DCD8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -2DCDA Length 000B (11) │ │ │ │ -2DCDC Version 01 (1) │ │ │ │ -2DCDD UID Size 04 (4) │ │ │ │ -2DCDE UID 00000000 (0) │ │ │ │ -2DCE2 GID Size 04 (4) │ │ │ │ -2DCE3 GID 00000000 (0) │ │ │ │ -2DCE7 PAYLOAD │ │ │ │ - │ │ │ │ -2ED64 LOCAL HEADER #21 04034B50 (67324752) │ │ │ │ -2ED68 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -2ED69 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -2ED6A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -2ED6C Compression Method 0008 (8) 'Deflated' │ │ │ │ -2ED6E Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -2ED72 CRC EDE5C0BB (3991257275) │ │ │ │ -2ED76 Compressed Size 00003BDA (15322) │ │ │ │ -2ED7A Uncompressed Size 0000D783 (55171) │ │ │ │ -2ED7E Filename Length 001D (29) │ │ │ │ -2ED80 Extra Length 001C (28) │ │ │ │ -2ED82 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x2ED82: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -2ED9F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -2EDA1 Length 0009 (9) │ │ │ │ -2EDA3 Flags 03 (3) 'Modification Access' │ │ │ │ -2EDA4 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -2EDA8 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -2EDAC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -2EDAE Length 000B (11) │ │ │ │ -2EDB0 Version 01 (1) │ │ │ │ -2EDB1 UID Size 04 (4) │ │ │ │ -2EDB2 UID 00000000 (0) │ │ │ │ -2EDB6 GID Size 04 (4) │ │ │ │ -2EDB7 GID 00000000 (0) │ │ │ │ -2EDBB PAYLOAD │ │ │ │ - │ │ │ │ -32995 LOCAL HEADER #22 04034B50 (67324752) │ │ │ │ -32999 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3299A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3299B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3299D Compression Method 0008 (8) 'Deflated' │ │ │ │ -3299F Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -329A3 CRC 9FDCAE48 (2682039880) │ │ │ │ -329A7 Compressed Size 00000D6B (3435) │ │ │ │ -329AB Uncompressed Size 0000388D (14477) │ │ │ │ -329AF Filename Length 001D (29) │ │ │ │ -329B1 Extra Length 001C (28) │ │ │ │ -329B3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x329B3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -329D0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -329D2 Length 0009 (9) │ │ │ │ -329D4 Flags 03 (3) 'Modification Access' │ │ │ │ -329D5 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -329D9 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -329DD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -329DF Length 000B (11) │ │ │ │ -329E1 Version 01 (1) │ │ │ │ -329E2 UID Size 04 (4) │ │ │ │ -329E3 UID 00000000 (0) │ │ │ │ -329E7 GID Size 04 (4) │ │ │ │ -329E8 GID 00000000 (0) │ │ │ │ -329EC PAYLOAD │ │ │ │ - │ │ │ │ -33757 LOCAL HEADER #23 04034B50 (67324752) │ │ │ │ -3375B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3375C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3375D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3375F Compression Method 0008 (8) 'Deflated' │ │ │ │ -33761 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -33765 CRC 009EB783 (10401667) │ │ │ │ -33769 Compressed Size 00001C6A (7274) │ │ │ │ -3376D Uncompressed Size 0000C186 (49542) │ │ │ │ -33771 Filename Length 001A (26) │ │ │ │ -33773 Extra Length 001C (28) │ │ │ │ -33775 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x33775: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3378F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -33791 Length 0009 (9) │ │ │ │ -33793 Flags 03 (3) 'Modification Access' │ │ │ │ -33794 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -33798 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -3379C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3379E Length 000B (11) │ │ │ │ -337A0 Version 01 (1) │ │ │ │ -337A1 UID Size 04 (4) │ │ │ │ -337A2 UID 00000000 (0) │ │ │ │ -337A6 GID Size 04 (4) │ │ │ │ -337A7 GID 00000000 (0) │ │ │ │ -337AB PAYLOAD │ │ │ │ - │ │ │ │ -35415 LOCAL HEADER #24 04034B50 (67324752) │ │ │ │ -35419 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3541A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3541B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3541D Compression Method 0008 (8) 'Deflated' │ │ │ │ -3541F Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -35423 CRC 04F1FDC5 (82968005) │ │ │ │ -35427 Compressed Size 000003DF (991) │ │ │ │ -3542B Uncompressed Size 00000935 (2357) │ │ │ │ -3542F Filename Length 0012 (18) │ │ │ │ -35431 Extra Length 001C (28) │ │ │ │ -35433 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x35433: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -35445 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -35447 Length 0009 (9) │ │ │ │ -35449 Flags 03 (3) 'Modification Access' │ │ │ │ -3544A Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -3544E Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -35452 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -35454 Length 000B (11) │ │ │ │ -35456 Version 01 (1) │ │ │ │ -35457 UID Size 04 (4) │ │ │ │ -35458 UID 00000000 (0) │ │ │ │ -3545C GID Size 04 (4) │ │ │ │ -3545D GID 00000000 (0) │ │ │ │ -35461 PAYLOAD │ │ │ │ - │ │ │ │ -35840 LOCAL HEADER #25 04034B50 (67324752) │ │ │ │ -35844 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -35845 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -35846 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -35848 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3584A Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -3584E CRC C605FB96 (3322280854) │ │ │ │ -35852 Compressed Size 000001D3 (467) │ │ │ │ -35856 Uncompressed Size 00000311 (785) │ │ │ │ -3585A Filename Length 0020 (32) │ │ │ │ -3585C Extra Length 001C (28) │ │ │ │ -3585E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3585E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3587E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -35880 Length 0009 (9) │ │ │ │ -35882 Flags 03 (3) 'Modification Access' │ │ │ │ -35883 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -35887 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -3588B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3588D Length 000B (11) │ │ │ │ -3588F Version 01 (1) │ │ │ │ -35890 UID Size 04 (4) │ │ │ │ -35891 UID 00000000 (0) │ │ │ │ -35895 GID Size 04 (4) │ │ │ │ -35896 GID 00000000 (0) │ │ │ │ -3589A PAYLOAD │ │ │ │ - │ │ │ │ -35A6D LOCAL HEADER #26 04034B50 (67324752) │ │ │ │ -35A71 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -35A72 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -35A73 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -35A75 Compression Method 0008 (8) 'Deflated' │ │ │ │ -35A77 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -35A7B CRC 3202CA1B (839043611) │ │ │ │ -35A7F Compressed Size 000017AA (6058) │ │ │ │ -35A83 Uncompressed Size 00009D18 (40216) │ │ │ │ -35A87 Filename Length 001B (27) │ │ │ │ -35A89 Extra Length 001C (28) │ │ │ │ -35A8B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x35A8B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -35AA6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -35AA8 Length 0009 (9) │ │ │ │ -35AAA Flags 03 (3) 'Modification Access' │ │ │ │ -35AAB Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -35AAF Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -35AB3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -35AB5 Length 000B (11) │ │ │ │ -35AB7 Version 01 (1) │ │ │ │ -35AB8 UID Size 04 (4) │ │ │ │ -35AB9 UID 00000000 (0) │ │ │ │ -35ABD GID Size 04 (4) │ │ │ │ -35ABE GID 00000000 (0) │ │ │ │ -35AC2 PAYLOAD │ │ │ │ - │ │ │ │ -3726C LOCAL HEADER #27 04034B50 (67324752) │ │ │ │ -37270 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -37271 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -37272 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -37274 Compression Method 0008 (8) 'Deflated' │ │ │ │ -37276 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -3727A CRC D6E9E108 (3605651720) │ │ │ │ -3727E Compressed Size 00001371 (4977) │ │ │ │ -37282 Uncompressed Size 00003B61 (15201) │ │ │ │ -37286 Filename Length 0015 (21) │ │ │ │ -37288 Extra Length 001C (28) │ │ │ │ -3728A Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3728A: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3729F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -372A1 Length 0009 (9) │ │ │ │ -372A3 Flags 03 (3) 'Modification Access' │ │ │ │ -372A4 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -372A8 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -372AC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -372AE Length 000B (11) │ │ │ │ -372B0 Version 01 (1) │ │ │ │ -372B1 UID Size 04 (4) │ │ │ │ -372B2 UID 00000000 (0) │ │ │ │ -372B6 GID Size 04 (4) │ │ │ │ -372B7 GID 00000000 (0) │ │ │ │ -372BB PAYLOAD │ │ │ │ - │ │ │ │ -3862C LOCAL HEADER #28 04034B50 (67324752) │ │ │ │ -38630 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -38631 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -38632 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -38634 Compression Method 0008 (8) 'Deflated' │ │ │ │ -38636 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -3863A CRC AD15BDB2 (2903883186) │ │ │ │ -3863E Compressed Size 00000AD0 (2768) │ │ │ │ -38642 Uncompressed Size 00002135 (8501) │ │ │ │ -38646 Filename Length 0011 (17) │ │ │ │ -38648 Extra Length 001C (28) │ │ │ │ -3864A Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3864A: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3865B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3865D Length 0009 (9) │ │ │ │ -3865F Flags 03 (3) 'Modification Access' │ │ │ │ -38660 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -38664 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -38668 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3866A Length 000B (11) │ │ │ │ -3866C Version 01 (1) │ │ │ │ -3866D UID Size 04 (4) │ │ │ │ -3866E UID 00000000 (0) │ │ │ │ -38672 GID Size 04 (4) │ │ │ │ -38673 GID 00000000 (0) │ │ │ │ -38677 PAYLOAD │ │ │ │ - │ │ │ │ -39147 LOCAL HEADER #29 04034B50 (67324752) │ │ │ │ -3914B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3914C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3914D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3914F Compression Method 0008 (8) 'Deflated' │ │ │ │ -39151 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -39155 CRC 47BA89D2 (1203407314) │ │ │ │ -39159 Compressed Size 000003FE (1022) │ │ │ │ -3915D Uncompressed Size 00000F0C (3852) │ │ │ │ -39161 Filename Length 0014 (20) │ │ │ │ -39163 Extra Length 001C (28) │ │ │ │ -39165 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x39165: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -39179 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3917B Length 0009 (9) │ │ │ │ -3917D Flags 03 (3) 'Modification Access' │ │ │ │ -3917E Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -39182 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -39186 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -39188 Length 000B (11) │ │ │ │ -3918A Version 01 (1) │ │ │ │ -3918B UID Size 04 (4) │ │ │ │ -3918C UID 00000000 (0) │ │ │ │ -39190 GID Size 04 (4) │ │ │ │ -39191 GID 00000000 (0) │ │ │ │ -39195 PAYLOAD │ │ │ │ - │ │ │ │ -39593 LOCAL HEADER #30 04034B50 (67324752) │ │ │ │ -39597 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -39598 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -39599 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3959B Compression Method 0008 (8) 'Deflated' │ │ │ │ -3959D Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -395A1 CRC F6397314 (4130960148) │ │ │ │ -395A5 Compressed Size 00001260 (4704) │ │ │ │ -395A9 Uncompressed Size 0000346B (13419) │ │ │ │ -395AD Filename Length 0014 (20) │ │ │ │ -395AF Extra Length 001C (28) │ │ │ │ -395B1 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x395B1: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -395C5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -395C7 Length 0009 (9) │ │ │ │ -395C9 Flags 03 (3) 'Modification Access' │ │ │ │ -395CA Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -395CE Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -395D2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -395D4 Length 000B (11) │ │ │ │ -395D6 Version 01 (1) │ │ │ │ -395D7 UID Size 04 (4) │ │ │ │ -395D8 UID 00000000 (0) │ │ │ │ -395DC GID Size 04 (4) │ │ │ │ -395DD GID 00000000 (0) │ │ │ │ -395E1 PAYLOAD │ │ │ │ - │ │ │ │ -3A841 LOCAL HEADER #31 04034B50 (67324752) │ │ │ │ -3A845 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3A846 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3A847 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3A849 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3A84B Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -3A84F CRC FB5774F3 (4216812787) │ │ │ │ -3A853 Compressed Size 00000ACF (2767) │ │ │ │ -3A857 Uncompressed Size 000022FF (8959) │ │ │ │ -3A85B Filename Length 001B (27) │ │ │ │ -3A85D Extra Length 001C (28) │ │ │ │ -3A85F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3A85F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3A87A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3A87C Length 0009 (9) │ │ │ │ -3A87E Flags 03 (3) 'Modification Access' │ │ │ │ -3A87F Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -3A883 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -3A887 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3A889 Length 000B (11) │ │ │ │ -3A88B Version 01 (1) │ │ │ │ -3A88C UID Size 04 (4) │ │ │ │ -3A88D UID 00000000 (0) │ │ │ │ -3A891 GID Size 04 (4) │ │ │ │ -3A892 GID 00000000 (0) │ │ │ │ -3A896 PAYLOAD │ │ │ │ - │ │ │ │ -3B365 LOCAL HEADER #32 04034B50 (67324752) │ │ │ │ -3B369 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3B36A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3B36B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3B36D Compression Method 0008 (8) 'Deflated' │ │ │ │ -3B36F Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -3B373 CRC 013FF3F3 (20968435) │ │ │ │ -3B377 Compressed Size 00000C4E (3150) │ │ │ │ -3B37B Uncompressed Size 0000273C (10044) │ │ │ │ -3B37F Filename Length 0013 (19) │ │ │ │ -3B381 Extra Length 001C (28) │ │ │ │ -3B383 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3B383: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3B396 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3B398 Length 0009 (9) │ │ │ │ -3B39A Flags 03 (3) 'Modification Access' │ │ │ │ -3B39B Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -3B39F Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -3B3A3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3B3A5 Length 000B (11) │ │ │ │ -3B3A7 Version 01 (1) │ │ │ │ -3B3A8 UID Size 04 (4) │ │ │ │ -3B3A9 UID 00000000 (0) │ │ │ │ -3B3AD GID Size 04 (4) │ │ │ │ -3B3AE GID 00000000 (0) │ │ │ │ -3B3B2 PAYLOAD │ │ │ │ - │ │ │ │ -3C000 LOCAL HEADER #33 04034B50 (67324752) │ │ │ │ -3C004 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3C005 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3C006 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3C008 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3C00A Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -3C00E CRC 0C6D74B6 (208499894) │ │ │ │ -3C012 Compressed Size 00000C94 (3220) │ │ │ │ -3C016 Uncompressed Size 00003D10 (15632) │ │ │ │ -3C01A Filename Length 0014 (20) │ │ │ │ -3C01C Extra Length 001C (28) │ │ │ │ -3C01E Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3C01E: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3C032 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3C034 Length 0009 (9) │ │ │ │ -3C036 Flags 03 (3) 'Modification Access' │ │ │ │ -3C037 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -3C03B Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -3C03F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3C041 Length 000B (11) │ │ │ │ -3C043 Version 01 (1) │ │ │ │ -3C044 UID Size 04 (4) │ │ │ │ -3C045 UID 00000000 (0) │ │ │ │ -3C049 GID Size 04 (4) │ │ │ │ -3C04A GID 00000000 (0) │ │ │ │ -3C04E PAYLOAD │ │ │ │ - │ │ │ │ -3CCE2 LOCAL HEADER #34 04034B50 (67324752) │ │ │ │ -3CCE6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3CCE7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3CCE8 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3CCEA Compression Method 0008 (8) 'Deflated' │ │ │ │ -3CCEC Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -3CCF0 CRC C090FFF6 (3230728182) │ │ │ │ -3CCF4 Compressed Size 00000F41 (3905) │ │ │ │ -3CCF8 Uncompressed Size 00003744 (14148) │ │ │ │ -3CCFC Filename Length 000F (15) │ │ │ │ -3CCFE Extra Length 001C (28) │ │ │ │ -3CD00 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3CD00: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3CD0F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3CD11 Length 0009 (9) │ │ │ │ -3CD13 Flags 03 (3) 'Modification Access' │ │ │ │ -3CD14 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -3CD18 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -3CD1C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3CD1E Length 000B (11) │ │ │ │ -3CD20 Version 01 (1) │ │ │ │ -3CD21 UID Size 04 (4) │ │ │ │ -3CD22 UID 00000000 (0) │ │ │ │ -3CD26 GID Size 04 (4) │ │ │ │ -3CD27 GID 00000000 (0) │ │ │ │ -3CD2B PAYLOAD │ │ │ │ - │ │ │ │ -3DC6C LOCAL HEADER #35 04034B50 (67324752) │ │ │ │ -3DC70 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3DC71 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3DC72 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3DC74 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3DC76 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -3DC7A CRC 1FC86902 (533227778) │ │ │ │ -3DC7E Compressed Size 000006BA (1722) │ │ │ │ -3DC82 Uncompressed Size 00001A79 (6777) │ │ │ │ -3DC86 Filename Length 000F (15) │ │ │ │ -3DC88 Extra Length 001C (28) │ │ │ │ -3DC8A Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3DC8A: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3DC99 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3DC9B Length 0009 (9) │ │ │ │ -3DC9D Flags 03 (3) 'Modification Access' │ │ │ │ -3DC9E Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -3DCA2 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -3DCA6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3DCA8 Length 000B (11) │ │ │ │ -3DCAA Version 01 (1) │ │ │ │ -3DCAB UID Size 04 (4) │ │ │ │ -3DCAC UID 00000000 (0) │ │ │ │ -3DCB0 GID Size 04 (4) │ │ │ │ -3DCB1 GID 00000000 (0) │ │ │ │ -3DCB5 PAYLOAD │ │ │ │ - │ │ │ │ -3E36F LOCAL HEADER #36 04034B50 (67324752) │ │ │ │ -3E373 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3E374 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3E375 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3E377 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3E379 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -3E37D CRC 6655DB7E (1716902782) │ │ │ │ -3E381 Compressed Size 00001A43 (6723) │ │ │ │ -3E385 Uncompressed Size 000064FA (25850) │ │ │ │ -3E389 Filename Length 0013 (19) │ │ │ │ -3E38B Extra Length 001C (28) │ │ │ │ -3E38D Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3E38D: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3E3A0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3E3A2 Length 0009 (9) │ │ │ │ -3E3A4 Flags 03 (3) 'Modification Access' │ │ │ │ -3E3A5 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -3E3A9 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -3E3AD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3E3AF Length 000B (11) │ │ │ │ -3E3B1 Version 01 (1) │ │ │ │ -3E3B2 UID Size 04 (4) │ │ │ │ -3E3B3 UID 00000000 (0) │ │ │ │ -3E3B7 GID Size 04 (4) │ │ │ │ -3E3B8 GID 00000000 (0) │ │ │ │ -3E3BC PAYLOAD │ │ │ │ - │ │ │ │ -3FDFF LOCAL HEADER #37 04034B50 (67324752) │ │ │ │ -3FE03 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3FE04 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3FE05 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3FE07 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3FE09 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -3FE0D CRC 8BADFA46 (2343434822) │ │ │ │ -3FE11 Compressed Size 000009A5 (2469) │ │ │ │ -3FE15 Uncompressed Size 00001B64 (7012) │ │ │ │ -3FE19 Filename Length 0010 (16) │ │ │ │ -3FE1B Extra Length 001C (28) │ │ │ │ -3FE1D Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3FE1D: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3FE2D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3FE2F Length 0009 (9) │ │ │ │ -3FE31 Flags 03 (3) 'Modification Access' │ │ │ │ -3FE32 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -3FE36 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -3FE3A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3FE3C Length 000B (11) │ │ │ │ -3FE3E Version 01 (1) │ │ │ │ -3FE3F UID Size 04 (4) │ │ │ │ -3FE40 UID 00000000 (0) │ │ │ │ -3FE44 GID Size 04 (4) │ │ │ │ -3FE45 GID 00000000 (0) │ │ │ │ -3FE49 PAYLOAD │ │ │ │ - │ │ │ │ -407EE LOCAL HEADER #38 04034B50 (67324752) │ │ │ │ -407F2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -407F3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -407F4 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -407F6 Compression Method 0008 (8) 'Deflated' │ │ │ │ -407F8 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -407FC CRC 5E895E09 (1586060809) │ │ │ │ -40800 Compressed Size 000006B6 (1718) │ │ │ │ -40804 Uncompressed Size 00001565 (5477) │ │ │ │ -40808 Filename Length 0012 (18) │ │ │ │ -4080A Extra Length 001C (28) │ │ │ │ -4080C Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4080C: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -4081E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -40820 Length 0009 (9) │ │ │ │ -40822 Flags 03 (3) 'Modification Access' │ │ │ │ -40823 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -40827 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -4082B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -4082D Length 000B (11) │ │ │ │ -4082F Version 01 (1) │ │ │ │ -40830 UID Size 04 (4) │ │ │ │ -40831 UID 00000000 (0) │ │ │ │ -40835 GID Size 04 (4) │ │ │ │ -40836 GID 00000000 (0) │ │ │ │ -4083A PAYLOAD │ │ │ │ - │ │ │ │ -40EF0 LOCAL HEADER #39 04034B50 (67324752) │ │ │ │ -40EF4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -40EF5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -40EF6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -40EF8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -40EFA Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -40EFE CRC 320A199B (839522715) │ │ │ │ -40F02 Compressed Size 00002D5C (11612) │ │ │ │ -40F06 Uncompressed Size 0000D07E (53374) │ │ │ │ -40F0A Filename Length 0010 (16) │ │ │ │ -40F0C Extra Length 001C (28) │ │ │ │ -40F0E Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x40F0E: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -40F1E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -40F20 Length 0009 (9) │ │ │ │ -40F22 Flags 03 (3) 'Modification Access' │ │ │ │ -40F23 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -40F27 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -40F2B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -40F2D Length 000B (11) │ │ │ │ -40F2F Version 01 (1) │ │ │ │ -40F30 UID Size 04 (4) │ │ │ │ -40F31 UID 00000000 (0) │ │ │ │ -40F35 GID Size 04 (4) │ │ │ │ -40F36 GID 00000000 (0) │ │ │ │ -40F3A PAYLOAD │ │ │ │ - │ │ │ │ -43C96 LOCAL HEADER #40 04034B50 (67324752) │ │ │ │ -43C9A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -43C9B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -43C9C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -43C9E Compression Method 0008 (8) 'Deflated' │ │ │ │ -43CA0 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -43CA4 CRC 83A14DD8 (2208386520) │ │ │ │ -43CA8 Compressed Size 00001E83 (7811) │ │ │ │ -43CAC Uncompressed Size 00009AAA (39594) │ │ │ │ -43CB0 Filename Length 0012 (18) │ │ │ │ -43CB2 Extra Length 001C (28) │ │ │ │ -43CB4 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x43CB4: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -43CC6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -43CC8 Length 0009 (9) │ │ │ │ -43CCA Flags 03 (3) 'Modification Access' │ │ │ │ -43CCB Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -43CCF Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -43CD3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -43CD5 Length 000B (11) │ │ │ │ -43CD7 Version 01 (1) │ │ │ │ -43CD8 UID Size 04 (4) │ │ │ │ -43CD9 UID 00000000 (0) │ │ │ │ -43CDD GID Size 04 (4) │ │ │ │ -43CDE GID 00000000 (0) │ │ │ │ -43CE2 PAYLOAD │ │ │ │ - │ │ │ │ -45B65 LOCAL HEADER #41 04034B50 (67324752) │ │ │ │ -45B69 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -45B6A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -45B6B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -45B6D Compression Method 0008 (8) 'Deflated' │ │ │ │ -45B6F Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -45B73 CRC E6CB5983 (3872086403) │ │ │ │ -45B77 Compressed Size 00001478 (5240) │ │ │ │ -45B7B Uncompressed Size 00007ACF (31439) │ │ │ │ -45B7F Filename Length 0018 (24) │ │ │ │ -45B81 Extra Length 001C (28) │ │ │ │ -45B83 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x45B83: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -45B9B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -45B9D Length 0009 (9) │ │ │ │ -45B9F Flags 03 (3) 'Modification Access' │ │ │ │ -45BA0 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -45BA4 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -45BA8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -45BAA Length 000B (11) │ │ │ │ -45BAC Version 01 (1) │ │ │ │ -45BAD UID Size 04 (4) │ │ │ │ -45BAE UID 00000000 (0) │ │ │ │ -45BB2 GID Size 04 (4) │ │ │ │ -45BB3 GID 00000000 (0) │ │ │ │ -45BB7 PAYLOAD │ │ │ │ - │ │ │ │ -4702F LOCAL HEADER #42 04034B50 (67324752) │ │ │ │ -47033 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -47034 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -47035 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -47037 Compression Method 0008 (8) 'Deflated' │ │ │ │ -47039 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -4703D CRC 17D9A72D (400140077) │ │ │ │ -47041 Compressed Size 000021E0 (8672) │ │ │ │ -47045 Uncompressed Size 0000D220 (53792) │ │ │ │ -47049 Filename Length 001F (31) │ │ │ │ -4704B Extra Length 001C (28) │ │ │ │ -4704D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4704D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -4706C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -4706E Length 0009 (9) │ │ │ │ -47070 Flags 03 (3) 'Modification Access' │ │ │ │ -47071 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -47075 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -47079 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -4707B Length 000B (11) │ │ │ │ -4707D Version 01 (1) │ │ │ │ -4707E UID Size 04 (4) │ │ │ │ -4707F UID 00000000 (0) │ │ │ │ -47083 GID Size 04 (4) │ │ │ │ -47084 GID 00000000 (0) │ │ │ │ -47088 PAYLOAD │ │ │ │ - │ │ │ │ -49268 LOCAL HEADER #43 04034B50 (67324752) │ │ │ │ -4926C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4926D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4926E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -49270 Compression Method 0008 (8) 'Deflated' │ │ │ │ -49272 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -49276 CRC E6EB8E0C (3874197004) │ │ │ │ -4927A Compressed Size 000003F7 (1015) │ │ │ │ -4927E Uncompressed Size 000008A3 (2211) │ │ │ │ -49282 Filename Length 001E (30) │ │ │ │ -49284 Extra Length 001C (28) │ │ │ │ -49286 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x49286: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -492A4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -492A6 Length 0009 (9) │ │ │ │ -492A8 Flags 03 (3) 'Modification Access' │ │ │ │ -492A9 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -492AD Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -492B1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -492B3 Length 000B (11) │ │ │ │ -492B5 Version 01 (1) │ │ │ │ -492B6 UID Size 04 (4) │ │ │ │ -492B7 UID 00000000 (0) │ │ │ │ -492BB GID Size 04 (4) │ │ │ │ -492BC GID 00000000 (0) │ │ │ │ -492C0 PAYLOAD │ │ │ │ - │ │ │ │ -496B7 LOCAL HEADER #44 04034B50 (67324752) │ │ │ │ -496BB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -496BC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -496BD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -496BF Compression Method 0008 (8) 'Deflated' │ │ │ │ -496C1 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -496C5 CRC 352C2657 (892085847) │ │ │ │ -496C9 Compressed Size 000042FE (17150) │ │ │ │ -496CD Uncompressed Size 0000DAE1 (56033) │ │ │ │ -496D1 Filename Length 0013 (19) │ │ │ │ -496D3 Extra Length 001C (28) │ │ │ │ -496D5 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x496D5: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -496E8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -496EA Length 0009 (9) │ │ │ │ -496EC Flags 03 (3) 'Modification Access' │ │ │ │ -496ED Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -496F1 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -496F5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -496F7 Length 000B (11) │ │ │ │ -496F9 Version 01 (1) │ │ │ │ -496FA UID Size 04 (4) │ │ │ │ -496FB UID 00000000 (0) │ │ │ │ -496FF GID Size 04 (4) │ │ │ │ -49700 GID 00000000 (0) │ │ │ │ -49704 PAYLOAD │ │ │ │ - │ │ │ │ -4DA02 LOCAL HEADER #45 04034B50 (67324752) │ │ │ │ -4DA06 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4DA07 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4DA08 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4DA0A Compression Method 0008 (8) 'Deflated' │ │ │ │ -4DA0C Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -4DA10 CRC 6505DF0C (1694883596) │ │ │ │ -4DA14 Compressed Size 000026C1 (9921) │ │ │ │ -4DA18 Uncompressed Size 00006E45 (28229) │ │ │ │ -4DA1C Filename Length 0019 (25) │ │ │ │ -4DA1E Extra Length 001C (28) │ │ │ │ -4DA20 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4DA20: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -4DA39 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -4DA3B Length 0009 (9) │ │ │ │ -4DA3D Flags 03 (3) 'Modification Access' │ │ │ │ -4DA3E Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -4DA42 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -4DA46 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -4DA48 Length 000B (11) │ │ │ │ -4DA4A Version 01 (1) │ │ │ │ -4DA4B UID Size 04 (4) │ │ │ │ -4DA4C UID 00000000 (0) │ │ │ │ -4DA50 GID Size 04 (4) │ │ │ │ -4DA51 GID 00000000 (0) │ │ │ │ -4DA55 PAYLOAD │ │ │ │ - │ │ │ │ -50116 LOCAL HEADER #46 04034B50 (67324752) │ │ │ │ -5011A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -5011B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -5011C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -5011E Compression Method 0008 (8) 'Deflated' │ │ │ │ -50120 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -50124 CRC BCB71496 (3166114966) │ │ │ │ -50128 Compressed Size 00002739 (10041) │ │ │ │ -5012C Uncompressed Size 00008B83 (35715) │ │ │ │ -50130 Filename Length 0019 (25) │ │ │ │ -50132 Extra Length 001C (28) │ │ │ │ -50134 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x50134: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -5014D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -5014F Length 0009 (9) │ │ │ │ -50151 Flags 03 (3) 'Modification Access' │ │ │ │ -50152 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -50156 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -5015A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -5015C Length 000B (11) │ │ │ │ -5015E Version 01 (1) │ │ │ │ -5015F UID Size 04 (4) │ │ │ │ -50160 UID 00000000 (0) │ │ │ │ -50164 GID Size 04 (4) │ │ │ │ -50165 GID 00000000 (0) │ │ │ │ -50169 PAYLOAD │ │ │ │ - │ │ │ │ -528A2 LOCAL HEADER #47 04034B50 (67324752) │ │ │ │ -528A6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -528A7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -528A8 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -528AA Compression Method 0008 (8) 'Deflated' │ │ │ │ -528AC Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -528B0 CRC 2F6969CF (795437519) │ │ │ │ -528B4 Compressed Size 00000ECD (3789) │ │ │ │ -528B8 Uncompressed Size 000053BF (21439) │ │ │ │ -528BC Filename Length 0021 (33) │ │ │ │ -528BE Extra Length 001C (28) │ │ │ │ -528C0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x528C0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -528E1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -528E3 Length 0009 (9) │ │ │ │ -528E5 Flags 03 (3) 'Modification Access' │ │ │ │ -528E6 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -528EA Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -528EE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -528F0 Length 000B (11) │ │ │ │ -528F2 Version 01 (1) │ │ │ │ -528F3 UID Size 04 (4) │ │ │ │ -528F4 UID 00000000 (0) │ │ │ │ -528F8 GID Size 04 (4) │ │ │ │ -528F9 GID 00000000 (0) │ │ │ │ -528FD PAYLOAD │ │ │ │ - │ │ │ │ -537CA LOCAL HEADER #48 04034B50 (67324752) │ │ │ │ -537CE Extract Zip Spec 14 (20) '2.0' │ │ │ │ -537CF Extract OS 00 (0) 'MS-DOS' │ │ │ │ -537D0 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -537D2 Compression Method 0008 (8) 'Deflated' │ │ │ │ -537D4 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -537D8 CRC 405AA2A7 (1079681703) │ │ │ │ -537DC Compressed Size 00000535 (1333) │ │ │ │ -537E0 Uncompressed Size 00000C96 (3222) │ │ │ │ -537E4 Filename Length 0017 (23) │ │ │ │ -537E6 Extra Length 001C (28) │ │ │ │ -537E8 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x537E8: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -537FF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -53801 Length 0009 (9) │ │ │ │ -53803 Flags 03 (3) 'Modification Access' │ │ │ │ -53804 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -53808 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -5380C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -5380E Length 000B (11) │ │ │ │ -53810 Version 01 (1) │ │ │ │ -53811 UID Size 04 (4) │ │ │ │ -53812 UID 00000000 (0) │ │ │ │ -53816 GID Size 04 (4) │ │ │ │ -53817 GID 00000000 (0) │ │ │ │ -5381B PAYLOAD │ │ │ │ - │ │ │ │ -53D50 LOCAL HEADER #49 04034B50 (67324752) │ │ │ │ -53D54 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -53D55 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -53D56 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -53D58 Compression Method 0008 (8) 'Deflated' │ │ │ │ -53D5A Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -53D5E CRC DB679B08 (3681000200) │ │ │ │ -53D62 Compressed Size 00000467 (1127) │ │ │ │ -53D66 Uncompressed Size 00000931 (2353) │ │ │ │ -53D6A Filename Length 001B (27) │ │ │ │ -53D6C Extra Length 001C (28) │ │ │ │ -53D6E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x53D6E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -53D89 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -53D8B Length 0009 (9) │ │ │ │ -53D8D Flags 03 (3) 'Modification Access' │ │ │ │ -53D8E Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -53D92 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -53D96 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -53D98 Length 000B (11) │ │ │ │ -53D9A Version 01 (1) │ │ │ │ -53D9B UID Size 04 (4) │ │ │ │ -53D9C UID 00000000 (0) │ │ │ │ -53DA0 GID Size 04 (4) │ │ │ │ -53DA1 GID 00000000 (0) │ │ │ │ -53DA5 PAYLOAD │ │ │ │ - │ │ │ │ -5420C LOCAL HEADER #50 04034B50 (67324752) │ │ │ │ -54210 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -54211 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -54212 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -54214 Compression Method 0008 (8) 'Deflated' │ │ │ │ -54216 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -5421A CRC 30DFE74E (819980110) │ │ │ │ -5421E Compressed Size 000016EE (5870) │ │ │ │ -54222 Uncompressed Size 00007A6D (31341) │ │ │ │ -54226 Filename Length 001F (31) │ │ │ │ -54228 Extra Length 001C (28) │ │ │ │ -5422A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x5422A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -54249 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -5424B Length 0009 (9) │ │ │ │ -5424D Flags 03 (3) 'Modification Access' │ │ │ │ -5424E Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -54252 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -54256 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -54258 Length 000B (11) │ │ │ │ -5425A Version 01 (1) │ │ │ │ -5425B UID Size 04 (4) │ │ │ │ -5425C UID 00000000 (0) │ │ │ │ -54260 GID Size 04 (4) │ │ │ │ -54261 GID 00000000 (0) │ │ │ │ -54265 PAYLOAD │ │ │ │ - │ │ │ │ -55953 LOCAL HEADER #51 04034B50 (67324752) │ │ │ │ -55957 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -55958 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -55959 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -5595B Compression Method 0008 (8) 'Deflated' │ │ │ │ -5595D Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -55961 CRC 30985937 (815290679) │ │ │ │ -55965 Compressed Size 00004163 (16739) │ │ │ │ -55969 Uncompressed Size 0001D15F (119135) │ │ │ │ -5596D Filename Length 0010 (16) │ │ │ │ -5596F Extra Length 001C (28) │ │ │ │ -55971 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x55971: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -55981 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -55983 Length 0009 (9) │ │ │ │ -55985 Flags 03 (3) 'Modification Access' │ │ │ │ -55986 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -5598A Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -5598E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -55990 Length 000B (11) │ │ │ │ -55992 Version 01 (1) │ │ │ │ -55993 UID Size 04 (4) │ │ │ │ -55994 UID 00000000 (0) │ │ │ │ -55998 GID Size 04 (4) │ │ │ │ -55999 GID 00000000 (0) │ │ │ │ -5599D PAYLOAD │ │ │ │ - │ │ │ │ -59B00 LOCAL HEADER #52 04034B50 (67324752) │ │ │ │ -59B04 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -59B05 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -59B06 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -59B08 Compression Method 0008 (8) 'Deflated' │ │ │ │ -59B0A Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -59B0E CRC E65F34EA (3864999146) │ │ │ │ -59B12 Compressed Size 00000AE6 (2790) │ │ │ │ -59B16 Uncompressed Size 00002229 (8745) │ │ │ │ -59B1A Filename Length 0014 (20) │ │ │ │ -59B1C Extra Length 001C (28) │ │ │ │ -59B1E Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x59B1E: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -59B32 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -59B34 Length 0009 (9) │ │ │ │ -59B36 Flags 03 (3) 'Modification Access' │ │ │ │ -59B37 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -59B3B Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -59B3F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -59B41 Length 000B (11) │ │ │ │ -59B43 Version 01 (1) │ │ │ │ -59B44 UID Size 04 (4) │ │ │ │ -59B45 UID 00000000 (0) │ │ │ │ -59B49 GID Size 04 (4) │ │ │ │ -59B4A GID 00000000 (0) │ │ │ │ -59B4E PAYLOAD │ │ │ │ - │ │ │ │ -5A634 LOCAL HEADER #53 04034B50 (67324752) │ │ │ │ -5A638 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -5A639 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -5A63A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -5A63C Compression Method 0008 (8) 'Deflated' │ │ │ │ -5A63E Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -5A642 CRC 3129212D (824779053) │ │ │ │ -5A646 Compressed Size 0000B54D (46413) │ │ │ │ -5A64A Uncompressed Size 000418FB (268539) │ │ │ │ -5A64E Filename Length 0017 (23) │ │ │ │ -5A650 Extra Length 001C (28) │ │ │ │ -5A652 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x5A652: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -5A669 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -5A66B Length 0009 (9) │ │ │ │ -5A66D Flags 03 (3) 'Modification Access' │ │ │ │ -5A66E Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -5A672 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -5A676 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -5A678 Length 000B (11) │ │ │ │ -5A67A Version 01 (1) │ │ │ │ -5A67B UID Size 04 (4) │ │ │ │ -5A67C UID 00000000 (0) │ │ │ │ -5A680 GID Size 04 (4) │ │ │ │ -5A681 GID 00000000 (0) │ │ │ │ -5A685 PAYLOAD │ │ │ │ - │ │ │ │ -65BD2 LOCAL HEADER #54 04034B50 (67324752) │ │ │ │ -65BD6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -65BD7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -65BD8 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -65BDA Compression Method 0008 (8) 'Deflated' │ │ │ │ -65BDC Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -65BE0 CRC 29267376 (690385782) │ │ │ │ -65BE4 Compressed Size 00000400 (1024) │ │ │ │ -65BE8 Uncompressed Size 0000093D (2365) │ │ │ │ -65BEC Filename Length 0013 (19) │ │ │ │ -65BEE Extra Length 001C (28) │ │ │ │ -65BF0 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x65BF0: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -65C03 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -65C05 Length 0009 (9) │ │ │ │ -65C07 Flags 03 (3) 'Modification Access' │ │ │ │ -65C08 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -65C0C Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -65C10 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -65C12 Length 000B (11) │ │ │ │ -65C14 Version 01 (1) │ │ │ │ -65C15 UID Size 04 (4) │ │ │ │ -65C16 UID 00000000 (0) │ │ │ │ -65C1A GID Size 04 (4) │ │ │ │ -65C1B GID 00000000 (0) │ │ │ │ -65C1F PAYLOAD │ │ │ │ - │ │ │ │ -6601F LOCAL HEADER #55 04034B50 (67324752) │ │ │ │ -66023 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -66024 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -66025 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -66027 Compression Method 0008 (8) 'Deflated' │ │ │ │ -66029 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -6602D CRC EEDA96E8 (4007302888) │ │ │ │ -66031 Compressed Size 000014D7 (5335) │ │ │ │ -66035 Uncompressed Size 00006892 (26770) │ │ │ │ -66039 Filename Length 0012 (18) │ │ │ │ -6603B Extra Length 001C (28) │ │ │ │ -6603D Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6603D: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6604F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -66051 Length 0009 (9) │ │ │ │ -66053 Flags 03 (3) 'Modification Access' │ │ │ │ -66054 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -66058 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -6605C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6605E Length 000B (11) │ │ │ │ -66060 Version 01 (1) │ │ │ │ -66061 UID Size 04 (4) │ │ │ │ -66062 UID 00000000 (0) │ │ │ │ -66066 GID Size 04 (4) │ │ │ │ -66067 GID 00000000 (0) │ │ │ │ -6606B PAYLOAD │ │ │ │ - │ │ │ │ -67542 LOCAL HEADER #56 04034B50 (67324752) │ │ │ │ -67546 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -67547 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -67548 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6754A Compression Method 0008 (8) 'Deflated' │ │ │ │ -6754C Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -67550 CRC F3842A14 (4085525012) │ │ │ │ -67554 Compressed Size 000011EA (4586) │ │ │ │ -67558 Uncompressed Size 000040FA (16634) │ │ │ │ -6755C Filename Length 0012 (18) │ │ │ │ -6755E Extra Length 001C (28) │ │ │ │ -67560 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x67560: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -67572 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -67574 Length 0009 (9) │ │ │ │ -67576 Flags 03 (3) 'Modification Access' │ │ │ │ -67577 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -6757B Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -6757F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -67581 Length 000B (11) │ │ │ │ -67583 Version 01 (1) │ │ │ │ -67584 UID Size 04 (4) │ │ │ │ -67585 UID 00000000 (0) │ │ │ │ -67589 GID Size 04 (4) │ │ │ │ -6758A GID 00000000 (0) │ │ │ │ -6758E PAYLOAD │ │ │ │ - │ │ │ │ -68778 LOCAL HEADER #57 04034B50 (67324752) │ │ │ │ -6877C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6877D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6877E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -68780 Compression Method 0008 (8) 'Deflated' │ │ │ │ -68782 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -68786 CRC A9CAB3BC (2848633788) │ │ │ │ -6878A Compressed Size 000009DA (2522) │ │ │ │ -6878E Uncompressed Size 00003529 (13609) │ │ │ │ -68792 Filename Length 0019 (25) │ │ │ │ -68794 Extra Length 001C (28) │ │ │ │ -68796 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x68796: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -687AF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -687B1 Length 0009 (9) │ │ │ │ -687B3 Flags 03 (3) 'Modification Access' │ │ │ │ -687B4 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -687B8 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -687BC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -687BE Length 000B (11) │ │ │ │ -687C0 Version 01 (1) │ │ │ │ -687C1 UID Size 04 (4) │ │ │ │ -687C2 UID 00000000 (0) │ │ │ │ -687C6 GID Size 04 (4) │ │ │ │ -687C7 GID 00000000 (0) │ │ │ │ -687CB PAYLOAD │ │ │ │ - │ │ │ │ -691A5 LOCAL HEADER #58 04034B50 (67324752) │ │ │ │ -691A9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -691AA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -691AB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -691AD Compression Method 0008 (8) 'Deflated' │ │ │ │ -691AF Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -691B3 CRC 6D5A95BB (1834653115) │ │ │ │ -691B7 Compressed Size 000018B8 (6328) │ │ │ │ -691BB Uncompressed Size 0000A678 (42616) │ │ │ │ -691BF Filename Length 0019 (25) │ │ │ │ -691C1 Extra Length 001C (28) │ │ │ │ -691C3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x691C3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -691DC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -691DE Length 0009 (9) │ │ │ │ -691E0 Flags 03 (3) 'Modification Access' │ │ │ │ -691E1 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -691E5 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -691E9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -691EB Length 000B (11) │ │ │ │ -691ED Version 01 (1) │ │ │ │ -691EE UID Size 04 (4) │ │ │ │ -691EF UID 00000000 (0) │ │ │ │ -691F3 GID Size 04 (4) │ │ │ │ -691F4 GID 00000000 (0) │ │ │ │ -691F8 PAYLOAD │ │ │ │ - │ │ │ │ -6AAB0 LOCAL HEADER #59 04034B50 (67324752) │ │ │ │ -6AAB4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6AAB5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6AAB6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6AAB8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -6AABA Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -6AABE CRC EB9538F3 (3952425203) │ │ │ │ -6AAC2 Compressed Size 0000177C (6012) │ │ │ │ -6AAC6 Uncompressed Size 0000472C (18220) │ │ │ │ -6AACA Filename Length 0014 (20) │ │ │ │ -6AACC Extra Length 001C (28) │ │ │ │ -6AACE Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6AACE: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6AAE2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6AAE4 Length 0009 (9) │ │ │ │ -6AAE6 Flags 03 (3) 'Modification Access' │ │ │ │ -6AAE7 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -6AAEB Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -6AAEF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6AAF1 Length 000B (11) │ │ │ │ -6AAF3 Version 01 (1) │ │ │ │ -6AAF4 UID Size 04 (4) │ │ │ │ -6AAF5 UID 00000000 (0) │ │ │ │ -6AAF9 GID Size 04 (4) │ │ │ │ -6AAFA GID 00000000 (0) │ │ │ │ -6AAFE PAYLOAD │ │ │ │ - │ │ │ │ -6C27A LOCAL HEADER #60 04034B50 (67324752) │ │ │ │ -6C27E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6C27F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6C280 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6C282 Compression Method 0008 (8) 'Deflated' │ │ │ │ -6C284 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -6C288 CRC F6F034FB (4142937339) │ │ │ │ -6C28C Compressed Size 00000409 (1033) │ │ │ │ -6C290 Uncompressed Size 00000825 (2085) │ │ │ │ -6C294 Filename Length 001C (28) │ │ │ │ -6C296 Extra Length 001C (28) │ │ │ │ -6C298 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6C298: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6C2B4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6C2B6 Length 0009 (9) │ │ │ │ -6C2B8 Flags 03 (3) 'Modification Access' │ │ │ │ -6C2B9 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -6C2BD Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -6C2C1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6C2C3 Length 000B (11) │ │ │ │ -6C2C5 Version 01 (1) │ │ │ │ -6C2C6 UID Size 04 (4) │ │ │ │ -6C2C7 UID 00000000 (0) │ │ │ │ -6C2CB GID Size 04 (4) │ │ │ │ -6C2CC GID 00000000 (0) │ │ │ │ -6C2D0 PAYLOAD │ │ │ │ - │ │ │ │ -6C6D9 LOCAL HEADER #61 04034B50 (67324752) │ │ │ │ -6C6DD Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6C6DE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6C6DF General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6C6E1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -6C6E3 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -6C6E7 CRC 40D420B6 (1087643830) │ │ │ │ -6C6EB Compressed Size 000024BB (9403) │ │ │ │ -6C6EF Uncompressed Size 0000B65B (46683) │ │ │ │ -6C6F3 Filename Length 001F (31) │ │ │ │ -6C6F5 Extra Length 001C (28) │ │ │ │ -6C6F7 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6C6F7: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6C716 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6C718 Length 0009 (9) │ │ │ │ -6C71A Flags 03 (3) 'Modification Access' │ │ │ │ -6C71B Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -6C71F Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -6C723 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6C725 Length 000B (11) │ │ │ │ -6C727 Version 01 (1) │ │ │ │ -6C728 UID Size 04 (4) │ │ │ │ -6C729 UID 00000000 (0) │ │ │ │ -6C72D GID Size 04 (4) │ │ │ │ -6C72E GID 00000000 (0) │ │ │ │ -6C732 PAYLOAD │ │ │ │ - │ │ │ │ -6EBED LOCAL HEADER #62 04034B50 (67324752) │ │ │ │ -6EBF1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6EBF2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6EBF3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6EBF5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -6EBF7 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -6EBFB CRC A8CFEE19 (2832199193) │ │ │ │ -6EBFF Compressed Size 00000E7E (3710) │ │ │ │ -6EC03 Uncompressed Size 000052D9 (21209) │ │ │ │ -6EC07 Filename Length 001F (31) │ │ │ │ -6EC09 Extra Length 001C (28) │ │ │ │ -6EC0B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6EC0B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6EC2A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6EC2C Length 0009 (9) │ │ │ │ -6EC2E Flags 03 (3) 'Modification Access' │ │ │ │ -6EC2F Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -6EC33 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -6EC37 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6EC39 Length 000B (11) │ │ │ │ -6EC3B Version 01 (1) │ │ │ │ -6EC3C UID Size 04 (4) │ │ │ │ -6EC3D UID 00000000 (0) │ │ │ │ -6EC41 GID Size 04 (4) │ │ │ │ -6EC42 GID 00000000 (0) │ │ │ │ -6EC46 PAYLOAD │ │ │ │ - │ │ │ │ -6FAC4 LOCAL HEADER #63 04034B50 (67324752) │ │ │ │ -6FAC8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6FAC9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6FACA General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6FACC Compression Method 0008 (8) 'Deflated' │ │ │ │ -6FACE Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -6FAD2 CRC D674A862 (3597969506) │ │ │ │ -6FAD6 Compressed Size 00000A44 (2628) │ │ │ │ -6FADA Uncompressed Size 0000247A (9338) │ │ │ │ -6FADE Filename Length 0013 (19) │ │ │ │ -6FAE0 Extra Length 001C (28) │ │ │ │ -6FAE2 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6FAE2: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6FAF5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6FAF7 Length 0009 (9) │ │ │ │ -6FAF9 Flags 03 (3) 'Modification Access' │ │ │ │ -6FAFA Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -6FAFE Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -6FB02 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6FB04 Length 000B (11) │ │ │ │ -6FB06 Version 01 (1) │ │ │ │ -6FB07 UID Size 04 (4) │ │ │ │ -6FB08 UID 00000000 (0) │ │ │ │ -6FB0C GID Size 04 (4) │ │ │ │ -6FB0D GID 00000000 (0) │ │ │ │ -6FB11 PAYLOAD │ │ │ │ - │ │ │ │ -70555 LOCAL HEADER #64 04034B50 (67324752) │ │ │ │ -70559 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -7055A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -7055B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -7055D Compression Method 0008 (8) 'Deflated' │ │ │ │ -7055F Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -70563 CRC 6F979343 (1872204611) │ │ │ │ -70567 Compressed Size 0000257B (9595) │ │ │ │ -7056B Uncompressed Size 0000BA5F (47711) │ │ │ │ -7056F Filename Length 0019 (25) │ │ │ │ -70571 Extra Length 001C (28) │ │ │ │ -70573 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x70573: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -7058C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -7058E Length 0009 (9) │ │ │ │ -70590 Flags 03 (3) 'Modification Access' │ │ │ │ -70591 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -70595 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -70599 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -7059B Length 000B (11) │ │ │ │ -7059D Version 01 (1) │ │ │ │ -7059E UID Size 04 (4) │ │ │ │ -7059F UID 00000000 (0) │ │ │ │ -705A3 GID Size 04 (4) │ │ │ │ -705A4 GID 00000000 (0) │ │ │ │ -705A8 PAYLOAD │ │ │ │ - │ │ │ │ -72B23 LOCAL HEADER #65 04034B50 (67324752) │ │ │ │ -72B27 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -72B28 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -72B29 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -72B2B Compression Method 0008 (8) 'Deflated' │ │ │ │ -72B2D Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -72B31 CRC DD1D8F22 (3709701922) │ │ │ │ -72B35 Compressed Size 00000EF8 (3832) │ │ │ │ -72B39 Uncompressed Size 00003A2C (14892) │ │ │ │ -72B3D Filename Length 0024 (36) │ │ │ │ -72B3F Extra Length 001C (28) │ │ │ │ -72B41 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x72B41: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -72B65 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -72B67 Length 0009 (9) │ │ │ │ -72B69 Flags 03 (3) 'Modification Access' │ │ │ │ -72B6A Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -72B6E Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -72B72 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -72B74 Length 000B (11) │ │ │ │ -72B76 Version 01 (1) │ │ │ │ -72B77 UID Size 04 (4) │ │ │ │ -72B78 UID 00000000 (0) │ │ │ │ -72B7C GID Size 04 (4) │ │ │ │ -72B7D GID 00000000 (0) │ │ │ │ -72B81 PAYLOAD │ │ │ │ - │ │ │ │ -73A79 LOCAL HEADER #66 04034B50 (67324752) │ │ │ │ -73A7D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -73A7E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -73A7F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -73A81 Compression Method 0008 (8) 'Deflated' │ │ │ │ -73A83 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -73A87 CRC C97A7AA4 (3380247204) │ │ │ │ -73A8B Compressed Size 00001AEB (6891) │ │ │ │ -73A8F Uncompressed Size 00005F82 (24450) │ │ │ │ -73A93 Filename Length 0017 (23) │ │ │ │ -73A95 Extra Length 001C (28) │ │ │ │ -73A97 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x73A97: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -73AAE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -73AB0 Length 0009 (9) │ │ │ │ -73AB2 Flags 03 (3) 'Modification Access' │ │ │ │ -73AB3 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -73AB7 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -73ABB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -73ABD Length 000B (11) │ │ │ │ -73ABF Version 01 (1) │ │ │ │ -73AC0 UID Size 04 (4) │ │ │ │ -73AC1 UID 00000000 (0) │ │ │ │ -73AC5 GID Size 04 (4) │ │ │ │ -73AC6 GID 00000000 (0) │ │ │ │ -73ACA PAYLOAD │ │ │ │ - │ │ │ │ -755B5 LOCAL HEADER #67 04034B50 (67324752) │ │ │ │ -755B9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -755BA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -755BB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -755BD Compression Method 0008 (8) 'Deflated' │ │ │ │ -755BF Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -755C3 CRC 11E32AF1 (300100337) │ │ │ │ -755C7 Compressed Size 00000ED3 (3795) │ │ │ │ -755CB Uncompressed Size 000038E2 (14562) │ │ │ │ -755CF Filename Length 0023 (35) │ │ │ │ -755D1 Extra Length 001C (28) │ │ │ │ -755D3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x755D3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -755F6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -755F8 Length 0009 (9) │ │ │ │ -755FA Flags 03 (3) 'Modification Access' │ │ │ │ -755FB Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -755FF Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -75603 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -75605 Length 000B (11) │ │ │ │ -75607 Version 01 (1) │ │ │ │ -75608 UID Size 04 (4) │ │ │ │ -75609 UID 00000000 (0) │ │ │ │ -7560D GID Size 04 (4) │ │ │ │ -7560E GID 00000000 (0) │ │ │ │ -75612 PAYLOAD │ │ │ │ - │ │ │ │ -764E5 LOCAL HEADER #68 04034B50 (67324752) │ │ │ │ -764E9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -764EA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -764EB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -764ED Compression Method 0008 (8) 'Deflated' │ │ │ │ -764EF Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -764F3 CRC 2DB7929F (767005343) │ │ │ │ -764F7 Compressed Size 00000113 (275) │ │ │ │ -764FB Uncompressed Size 000001F3 (499) │ │ │ │ -764FF Filename Length 001B (27) │ │ │ │ -76501 Extra Length 001C (28) │ │ │ │ -76503 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x76503: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -7651E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -76520 Length 0009 (9) │ │ │ │ -76522 Flags 03 (3) 'Modification Access' │ │ │ │ -76523 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -76527 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -7652B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -7652D Length 000B (11) │ │ │ │ -7652F Version 01 (1) │ │ │ │ -76530 UID Size 04 (4) │ │ │ │ -76531 UID 00000000 (0) │ │ │ │ -76535 GID Size 04 (4) │ │ │ │ -76536 GID 00000000 (0) │ │ │ │ -7653A PAYLOAD │ │ │ │ - │ │ │ │ -7664D LOCAL HEADER #69 04034B50 (67324752) │ │ │ │ -76651 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -76652 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -76653 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -76655 Compression Method 0008 (8) 'Deflated' │ │ │ │ -76657 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -7665B CRC 852C941F (2234291231) │ │ │ │ -7665F Compressed Size 00001891 (6289) │ │ │ │ -76663 Uncompressed Size 00008FAC (36780) │ │ │ │ -76667 Filename Length 001D (29) │ │ │ │ -76669 Extra Length 001C (28) │ │ │ │ -7666B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x7666B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -76688 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -7668A Length 0009 (9) │ │ │ │ -7668C Flags 03 (3) 'Modification Access' │ │ │ │ -7668D Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -76691 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -76695 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -76697 Length 000B (11) │ │ │ │ -76699 Version 01 (1) │ │ │ │ -7669A UID Size 04 (4) │ │ │ │ -7669B UID 00000000 (0) │ │ │ │ -7669F GID Size 04 (4) │ │ │ │ -766A0 GID 00000000 (0) │ │ │ │ -766A4 PAYLOAD │ │ │ │ - │ │ │ │ -77F35 LOCAL HEADER #70 04034B50 (67324752) │ │ │ │ -77F39 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -77F3A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -77F3B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -77F3D Compression Method 0008 (8) 'Deflated' │ │ │ │ -77F3F Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -77F43 CRC 905D47D7 (2422032343) │ │ │ │ -77F47 Compressed Size 0000164C (5708) │ │ │ │ -77F4B Uncompressed Size 00003A9B (15003) │ │ │ │ -77F4F Filename Length 0015 (21) │ │ │ │ -77F51 Extra Length 001C (28) │ │ │ │ -77F53 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x77F53: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -77F68 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -77F6A Length 0009 (9) │ │ │ │ -77F6C Flags 03 (3) 'Modification Access' │ │ │ │ -77F6D Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -77F71 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -77F75 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -77F77 Length 000B (11) │ │ │ │ -77F79 Version 01 (1) │ │ │ │ -77F7A UID Size 04 (4) │ │ │ │ -77F7B UID 00000000 (0) │ │ │ │ -77F7F GID Size 04 (4) │ │ │ │ -77F80 GID 00000000 (0) │ │ │ │ -77F84 PAYLOAD │ │ │ │ - │ │ │ │ -795D0 LOCAL HEADER #71 04034B50 (67324752) │ │ │ │ -795D4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -795D5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -795D6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -795D8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -795DA Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -795DE CRC 42AB8589 (1118537097) │ │ │ │ -795E2 Compressed Size 000040B8 (16568) │ │ │ │ -795E6 Uncompressed Size 00013397 (78743) │ │ │ │ -795EA Filename Length 0016 (22) │ │ │ │ -795EC Extra Length 001C (28) │ │ │ │ -795EE Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x795EE: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -79604 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -79606 Length 0009 (9) │ │ │ │ -79608 Flags 03 (3) 'Modification Access' │ │ │ │ -79609 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -7960D Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -79611 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -79613 Length 000B (11) │ │ │ │ -79615 Version 01 (1) │ │ │ │ -79616 UID Size 04 (4) │ │ │ │ -79617 UID 00000000 (0) │ │ │ │ -7961B GID Size 04 (4) │ │ │ │ -7961C GID 00000000 (0) │ │ │ │ -79620 PAYLOAD │ │ │ │ - │ │ │ │ -7D6D8 LOCAL HEADER #72 04034B50 (67324752) │ │ │ │ -7D6DC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -7D6DD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -7D6DE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -7D6E0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -7D6E2 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -7D6E6 CRC C0168E2C (3222703660) │ │ │ │ -7D6EA Compressed Size 00003E83 (16003) │ │ │ │ -7D6EE Uncompressed Size 0001C17B (115067) │ │ │ │ -7D6F2 Filename Length 0019 (25) │ │ │ │ -7D6F4 Extra Length 001C (28) │ │ │ │ -7D6F6 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x7D6F6: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -7D70F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -7D711 Length 0009 (9) │ │ │ │ -7D713 Flags 03 (3) 'Modification Access' │ │ │ │ -7D714 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -7D718 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -7D71C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -7D71E Length 000B (11) │ │ │ │ -7D720 Version 01 (1) │ │ │ │ -7D721 UID Size 04 (4) │ │ │ │ -7D722 UID 00000000 (0) │ │ │ │ -7D726 GID Size 04 (4) │ │ │ │ -7D727 GID 00000000 (0) │ │ │ │ -7D72B PAYLOAD │ │ │ │ - │ │ │ │ -815AE LOCAL HEADER #73 04034B50 (67324752) │ │ │ │ -815B2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -815B3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -815B4 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -815B6 Compression Method 0008 (8) 'Deflated' │ │ │ │ -815B8 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -815BC CRC B11E2924 (2971543844) │ │ │ │ -815C0 Compressed Size 0000088C (2188) │ │ │ │ -815C4 Uncompressed Size 0000362E (13870) │ │ │ │ -815C8 Filename Length 0011 (17) │ │ │ │ -815CA Extra Length 001C (28) │ │ │ │ -815CC Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x815CC: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -815DD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -815DF Length 0009 (9) │ │ │ │ -815E1 Flags 03 (3) 'Modification Access' │ │ │ │ -815E2 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -815E6 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -815EA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -815EC Length 000B (11) │ │ │ │ -815EE Version 01 (1) │ │ │ │ -815EF UID Size 04 (4) │ │ │ │ -815F0 UID 00000000 (0) │ │ │ │ -815F4 GID Size 04 (4) │ │ │ │ -815F5 GID 00000000 (0) │ │ │ │ -815F9 PAYLOAD │ │ │ │ - │ │ │ │ -81E85 LOCAL HEADER #74 04034B50 (67324752) │ │ │ │ -81E89 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -81E8A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -81E8B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -81E8D Compression Method 0008 (8) 'Deflated' │ │ │ │ -81E8F Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -81E93 CRC B4CB7C77 (3033234551) │ │ │ │ -81E97 Compressed Size 000051C3 (20931) │ │ │ │ -81E9B Uncompressed Size 0001FBF7 (130039) │ │ │ │ -81E9F Filename Length 0015 (21) │ │ │ │ -81EA1 Extra Length 001C (28) │ │ │ │ -81EA3 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x81EA3: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -81EB8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -81EBA Length 0009 (9) │ │ │ │ -81EBC Flags 03 (3) 'Modification Access' │ │ │ │ -81EBD Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -81EC1 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -81EC5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -81EC7 Length 000B (11) │ │ │ │ -81EC9 Version 01 (1) │ │ │ │ -81ECA UID Size 04 (4) │ │ │ │ -81ECB UID 00000000 (0) │ │ │ │ -81ECF GID Size 04 (4) │ │ │ │ -81ED0 GID 00000000 (0) │ │ │ │ -81ED4 PAYLOAD │ │ │ │ - │ │ │ │ -87097 LOCAL HEADER #75 04034B50 (67324752) │ │ │ │ -8709B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8709C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8709D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8709F Compression Method 0008 (8) 'Deflated' │ │ │ │ -870A1 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -870A5 CRC C7EDAA9C (3354241692) │ │ │ │ -870A9 Compressed Size 00001C42 (7234) │ │ │ │ -870AD Uncompressed Size 00008AC2 (35522) │ │ │ │ -870B1 Filename Length 0019 (25) │ │ │ │ -870B3 Extra Length 001C (28) │ │ │ │ -870B5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x870B5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -870CE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -870D0 Length 0009 (9) │ │ │ │ -870D2 Flags 03 (3) 'Modification Access' │ │ │ │ -870D3 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -870D7 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -870DB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -870DD Length 000B (11) │ │ │ │ -870DF Version 01 (1) │ │ │ │ -870E0 UID Size 04 (4) │ │ │ │ -870E1 UID 00000000 (0) │ │ │ │ -870E5 GID Size 04 (4) │ │ │ │ -870E6 GID 00000000 (0) │ │ │ │ -870EA PAYLOAD │ │ │ │ - │ │ │ │ -88D2C LOCAL HEADER #76 04034B50 (67324752) │ │ │ │ -88D30 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -88D31 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -88D32 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -88D34 Compression Method 0008 (8) 'Deflated' │ │ │ │ -88D36 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -88D3A CRC 453D1C3A (1161632826) │ │ │ │ -88D3E Compressed Size 00000D95 (3477) │ │ │ │ -88D42 Uncompressed Size 00002E9F (11935) │ │ │ │ -88D46 Filename Length 0018 (24) │ │ │ │ -88D48 Extra Length 001C (28) │ │ │ │ -88D4A Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x88D4A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -88D62 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -88D64 Length 0009 (9) │ │ │ │ -88D66 Flags 03 (3) 'Modification Access' │ │ │ │ -88D67 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -88D6B Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -88D6F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -88D71 Length 000B (11) │ │ │ │ -88D73 Version 01 (1) │ │ │ │ -88D74 UID Size 04 (4) │ │ │ │ -88D75 UID 00000000 (0) │ │ │ │ -88D79 GID Size 04 (4) │ │ │ │ -88D7A GID 00000000 (0) │ │ │ │ -88D7E PAYLOAD │ │ │ │ - │ │ │ │ -89B13 LOCAL HEADER #77 04034B50 (67324752) │ │ │ │ -89B17 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -89B18 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -89B19 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -89B1B Compression Method 0008 (8) 'Deflated' │ │ │ │ -89B1D Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -89B21 CRC E5DE5170 (3856552304) │ │ │ │ -89B25 Compressed Size 000001DF (479) │ │ │ │ -89B29 Uncompressed Size 00000323 (803) │ │ │ │ -89B2D Filename Length 0011 (17) │ │ │ │ -89B2F Extra Length 001C (28) │ │ │ │ -89B31 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x89B31: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -89B42 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -89B44 Length 0009 (9) │ │ │ │ -89B46 Flags 03 (3) 'Modification Access' │ │ │ │ -89B47 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -89B4B Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -89B4F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -89B51 Length 000B (11) │ │ │ │ -89B53 Version 01 (1) │ │ │ │ -89B54 UID Size 04 (4) │ │ │ │ -89B55 UID 00000000 (0) │ │ │ │ -89B59 GID Size 04 (4) │ │ │ │ -89B5A GID 00000000 (0) │ │ │ │ -89B5E PAYLOAD │ │ │ │ - │ │ │ │ -89D3D LOCAL HEADER #78 04034B50 (67324752) │ │ │ │ -89D41 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -89D42 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -89D43 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -89D45 Compression Method 0008 (8) 'Deflated' │ │ │ │ -89D47 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -89D4B CRC AA4987E4 (2856945636) │ │ │ │ -89D4F Compressed Size 000006BD (1725) │ │ │ │ -89D53 Uncompressed Size 0000141E (5150) │ │ │ │ -89D57 Filename Length 0019 (25) │ │ │ │ -89D59 Extra Length 001C (28) │ │ │ │ -89D5B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x89D5B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -89D74 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -89D76 Length 0009 (9) │ │ │ │ -89D78 Flags 03 (3) 'Modification Access' │ │ │ │ -89D79 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -89D7D Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -89D81 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -89D83 Length 000B (11) │ │ │ │ -89D85 Version 01 (1) │ │ │ │ -89D86 UID Size 04 (4) │ │ │ │ -89D87 UID 00000000 (0) │ │ │ │ -89D8B GID Size 04 (4) │ │ │ │ -89D8C GID 00000000 (0) │ │ │ │ -89D90 PAYLOAD │ │ │ │ - │ │ │ │ -8A44D LOCAL HEADER #79 04034B50 (67324752) │ │ │ │ -8A451 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8A452 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8A453 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8A455 Compression Method 0008 (8) 'Deflated' │ │ │ │ -8A457 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -8A45B CRC CF6FE96A (3480217962) │ │ │ │ -8A45F Compressed Size 00001B8B (7051) │ │ │ │ -8A463 Uncompressed Size 00009F5F (40799) │ │ │ │ -8A467 Filename Length 0018 (24) │ │ │ │ -8A469 Extra Length 001C (28) │ │ │ │ -8A46B Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8A46B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8A483 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -8A485 Length 0009 (9) │ │ │ │ -8A487 Flags 03 (3) 'Modification Access' │ │ │ │ -8A488 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -8A48C Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -8A490 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8A492 Length 000B (11) │ │ │ │ -8A494 Version 01 (1) │ │ │ │ -8A495 UID Size 04 (4) │ │ │ │ -8A496 UID 00000000 (0) │ │ │ │ -8A49A GID Size 04 (4) │ │ │ │ -8A49B GID 00000000 (0) │ │ │ │ -8A49F PAYLOAD │ │ │ │ - │ │ │ │ -8C02A LOCAL HEADER #80 04034B50 (67324752) │ │ │ │ -8C02E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8C02F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8C030 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8C032 Compression Method 0008 (8) 'Deflated' │ │ │ │ -8C034 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -8C038 CRC 3D2C86BB (1026328251) │ │ │ │ -8C03C Compressed Size 00001702 (5890) │ │ │ │ -8C040 Uncompressed Size 00008B12 (35602) │ │ │ │ -8C044 Filename Length 0012 (18) │ │ │ │ -8C046 Extra Length 001C (28) │ │ │ │ -8C048 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8C048: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8C05A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -8C05C Length 0009 (9) │ │ │ │ -8C05E Flags 03 (3) 'Modification Access' │ │ │ │ -8C05F Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -8C063 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -8C067 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8C069 Length 000B (11) │ │ │ │ -8C06B Version 01 (1) │ │ │ │ -8C06C UID Size 04 (4) │ │ │ │ -8C06D UID 00000000 (0) │ │ │ │ -8C071 GID Size 04 (4) │ │ │ │ -8C072 GID 00000000 (0) │ │ │ │ -8C076 PAYLOAD │ │ │ │ - │ │ │ │ -8D778 LOCAL HEADER #81 04034B50 (67324752) │ │ │ │ -8D77C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8D77D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8D77E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8D780 Compression Method 0008 (8) 'Deflated' │ │ │ │ -8D782 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -8D786 CRC 27EFA83C (670017596) │ │ │ │ -8D78A Compressed Size 00001E0D (7693) │ │ │ │ -8D78E Uncompressed Size 00008803 (34819) │ │ │ │ -8D792 Filename Length 0016 (22) │ │ │ │ -8D794 Extra Length 001C (28) │ │ │ │ -8D796 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8D796: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8D7AC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -8D7AE Length 0009 (9) │ │ │ │ -8D7B0 Flags 03 (3) 'Modification Access' │ │ │ │ -8D7B1 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -8D7B5 Access Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -8D7B9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8D7BB Length 000B (11) │ │ │ │ -8D7BD Version 01 (1) │ │ │ │ -8D7BE UID Size 04 (4) │ │ │ │ -8D7BF UID 00000000 (0) │ │ │ │ -8D7C3 GID Size 04 (4) │ │ │ │ -8D7C4 GID 00000000 (0) │ │ │ │ -8D7C8 PAYLOAD │ │ │ │ - │ │ │ │ -8F5D5 LOCAL HEADER #82 04034B50 (67324752) │ │ │ │ -8F5D9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8F5DA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8F5DB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8F5DD Compression Method 0008 (8) 'Deflated' │ │ │ │ -8F5DF Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -8F5E3 CRC 6E962EB6 (1855336118) │ │ │ │ -8F5E7 Compressed Size 000029A8 (10664) │ │ │ │ -8F5EB Uncompressed Size 0000D04F (53327) │ │ │ │ -8F5EF Filename Length 001A (26) │ │ │ │ -8F5F1 Extra Length 001C (28) │ │ │ │ -8F5F3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8F5F3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8F60D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -8F60F Length 0009 (9) │ │ │ │ -8F611 Flags 03 (3) 'Modification Access' │ │ │ │ -8F612 Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -8F616 Access Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -8F61A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8F61C Length 000B (11) │ │ │ │ -8F61E Version 01 (1) │ │ │ │ -8F61F UID Size 04 (4) │ │ │ │ -8F620 UID 00000000 (0) │ │ │ │ -8F624 GID Size 04 (4) │ │ │ │ -8F625 GID 00000000 (0) │ │ │ │ -8F629 PAYLOAD │ │ │ │ - │ │ │ │ -91FD1 LOCAL HEADER #83 04034B50 (67324752) │ │ │ │ -91FD5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -91FD6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -91FD7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -91FD9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -91FDB Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -91FDF CRC E9ED2192 (3924631954) │ │ │ │ -91FE3 Compressed Size 000009AA (2474) │ │ │ │ -91FE7 Uncompressed Size 00001DB6 (7606) │ │ │ │ -91FEB Filename Length 0018 (24) │ │ │ │ -91FED Extra Length 001C (28) │ │ │ │ -91FEF Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x91FEF: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -92007 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -92009 Length 0009 (9) │ │ │ │ -9200B Flags 03 (3) 'Modification Access' │ │ │ │ -9200C Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -92010 Access Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -92014 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -92016 Length 000B (11) │ │ │ │ -92018 Version 01 (1) │ │ │ │ -92019 UID Size 04 (4) │ │ │ │ -9201A UID 00000000 (0) │ │ │ │ -9201E GID Size 04 (4) │ │ │ │ -9201F GID 00000000 (0) │ │ │ │ -92023 PAYLOAD │ │ │ │ - │ │ │ │ -929CD LOCAL HEADER #84 04034B50 (67324752) │ │ │ │ -929D1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -929D2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -929D3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -929D5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -929D7 Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -929DB CRC F0556E9A (4032130714) │ │ │ │ -929DF Compressed Size 000152EE (86766) │ │ │ │ -929E3 Uncompressed Size 000159F8 (88568) │ │ │ │ -929E7 Filename Length 001E (30) │ │ │ │ -929E9 Extra Length 001C (28) │ │ │ │ -929EB Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x929EB: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -92A09 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -92A0B Length 0009 (9) │ │ │ │ -92A0D Flags 03 (3) 'Modification Access' │ │ │ │ -92A0E Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -92A12 Access Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -92A16 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -92A18 Length 000B (11) │ │ │ │ -92A1A Version 01 (1) │ │ │ │ -92A1B UID Size 04 (4) │ │ │ │ -92A1C UID 00000000 (0) │ │ │ │ -92A20 GID Size 04 (4) │ │ │ │ -92A21 GID 00000000 (0) │ │ │ │ -92A25 PAYLOAD │ │ │ │ - │ │ │ │ -A7D13 LOCAL HEADER #85 04034B50 (67324752) │ │ │ │ -A7D17 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A7D18 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A7D19 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A7D1B Compression Method 0008 (8) 'Deflated' │ │ │ │ -A7D1D Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -A7D21 CRC F5E2129F (4125233823) │ │ │ │ -A7D25 Compressed Size 000016BC (5820) │ │ │ │ -A7D29 Uncompressed Size 000016CD (5837) │ │ │ │ -A7D2D Filename Length 0015 (21) │ │ │ │ -A7D2F Extra Length 001C (28) │ │ │ │ -A7D31 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA7D31: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A7D46 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A7D48 Length 0009 (9) │ │ │ │ -A7D4A Flags 03 (3) 'Modification Access' │ │ │ │ -A7D4B Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -A7D4F Access Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -A7D53 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A7D55 Length 000B (11) │ │ │ │ -A7D57 Version 01 (1) │ │ │ │ -A7D58 UID Size 04 (4) │ │ │ │ -A7D59 UID 00000000 (0) │ │ │ │ -A7D5D GID Size 04 (4) │ │ │ │ -A7D5E GID 00000000 (0) │ │ │ │ -A7D62 PAYLOAD │ │ │ │ - │ │ │ │ -A941E LOCAL HEADER #86 04034B50 (67324752) │ │ │ │ -A9422 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A9423 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A9424 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A9426 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A9428 Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -A942C CRC F5E2129F (4125233823) │ │ │ │ -A9430 Compressed Size 000016BC (5820) │ │ │ │ -A9434 Uncompressed Size 000016CD (5837) │ │ │ │ -A9438 Filename Length 001C (28) │ │ │ │ -A943A Extra Length 001C (28) │ │ │ │ -A943C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA943C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A9458 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A945A Length 0009 (9) │ │ │ │ -A945C Flags 03 (3) 'Modification Access' │ │ │ │ -A945D Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -A9461 Access Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -A9465 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A9467 Length 000B (11) │ │ │ │ -A9469 Version 01 (1) │ │ │ │ -A946A UID Size 04 (4) │ │ │ │ -A946B UID 00000000 (0) │ │ │ │ -A946F GID Size 04 (4) │ │ │ │ -A9470 GID 00000000 (0) │ │ │ │ -A9474 PAYLOAD │ │ │ │ - │ │ │ │ -AAB30 LOCAL HEADER #87 04034B50 (67324752) │ │ │ │ -AAB34 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -AAB35 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -AAB36 General Purpose Flag 0000 (0) │ │ │ │ -AAB38 Compression Method 0000 (0) 'Stored' │ │ │ │ -AAB3A Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -AAB3E CRC FC95F24B (4237685323) │ │ │ │ -AAB42 Compressed Size 00001B84 (7044) │ │ │ │ -AAB46 Uncompressed Size 00001B84 (7044) │ │ │ │ -AAB4A Filename Length 0016 (22) │ │ │ │ -AAB4C Extra Length 001C (28) │ │ │ │ -AAB4E Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xAAB4E: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -AAB64 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -AAB66 Length 0009 (9) │ │ │ │ -AAB68 Flags 03 (3) 'Modification Access' │ │ │ │ -AAB69 Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -AAB6D Access Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -AAB71 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -AAB73 Length 000B (11) │ │ │ │ -AAB75 Version 01 (1) │ │ │ │ -AAB76 UID Size 04 (4) │ │ │ │ -AAB77 UID 00000000 (0) │ │ │ │ -AAB7B GID Size 04 (4) │ │ │ │ -AAB7C GID 00000000 (0) │ │ │ │ -AAB80 PAYLOAD │ │ │ │ - │ │ │ │ -AC704 LOCAL HEADER #88 04034B50 (67324752) │ │ │ │ -AC708 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -AC709 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -AC70A General Purpose Flag 0000 (0) │ │ │ │ -AC70C Compression Method 0000 (0) 'Stored' │ │ │ │ -AC70E Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -AC712 CRC D0D71F86 (3503759238) │ │ │ │ -AC716 Compressed Size 00000B7B (2939) │ │ │ │ -AC71A Uncompressed Size 00000B7B (2939) │ │ │ │ -AC71E Filename Length 0016 (22) │ │ │ │ -AC720 Extra Length 001C (28) │ │ │ │ -AC722 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xAC722: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -AC738 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -AC73A Length 0009 (9) │ │ │ │ -AC73C Flags 03 (3) 'Modification Access' │ │ │ │ -AC73D Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -AC741 Access Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -AC745 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -AC747 Length 000B (11) │ │ │ │ -AC749 Version 01 (1) │ │ │ │ -AC74A UID Size 04 (4) │ │ │ │ -AC74B UID 00000000 (0) │ │ │ │ -AC74F GID Size 04 (4) │ │ │ │ -AC750 GID 00000000 (0) │ │ │ │ -AC754 PAYLOAD │ │ │ │ - │ │ │ │ -AD2CF LOCAL HEADER #89 04034B50 (67324752) │ │ │ │ -AD2D3 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -AD2D4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -AD2D5 General Purpose Flag 0000 (0) │ │ │ │ -AD2D7 Compression Method 0000 (0) 'Stored' │ │ │ │ -AD2D9 Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -AD2DD CRC FFF9C4D2 (4294558930) │ │ │ │ -AD2E1 Compressed Size 0000138F (5007) │ │ │ │ -AD2E5 Uncompressed Size 0000138F (5007) │ │ │ │ -AD2E9 Filename Length 0016 (22) │ │ │ │ -AD2EB Extra Length 001C (28) │ │ │ │ -AD2ED Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xAD2ED: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -AD303 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -AD305 Length 0009 (9) │ │ │ │ -AD307 Flags 03 (3) 'Modification Access' │ │ │ │ -AD308 Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -AD30C Access Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -AD310 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -AD312 Length 000B (11) │ │ │ │ -AD314 Version 01 (1) │ │ │ │ -AD315 UID Size 04 (4) │ │ │ │ -AD316 UID 00000000 (0) │ │ │ │ -AD31A GID Size 04 (4) │ │ │ │ -AD31B GID 00000000 (0) │ │ │ │ -AD31F PAYLOAD │ │ │ │ - │ │ │ │ -AE6AE LOCAL HEADER #90 04034B50 (67324752) │ │ │ │ -AE6B2 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -AE6B3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -AE6B4 General Purpose Flag 0000 (0) │ │ │ │ -AE6B6 Compression Method 0000 (0) 'Stored' │ │ │ │ -AE6B8 Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -AE6BC CRC A1037E8E (2701360782) │ │ │ │ -AE6C0 Compressed Size 0000145E (5214) │ │ │ │ -AE6C4 Uncompressed Size 0000145E (5214) │ │ │ │ -AE6C8 Filename Length 0016 (22) │ │ │ │ -AE6CA Extra Length 001C (28) │ │ │ │ -AE6CC Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xAE6CC: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -AE6E2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -AE6E4 Length 0009 (9) │ │ │ │ -AE6E6 Flags 03 (3) 'Modification Access' │ │ │ │ -AE6E7 Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -AE6EB Access Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -AE6EF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -AE6F1 Length 000B (11) │ │ │ │ -AE6F3 Version 01 (1) │ │ │ │ -AE6F4 UID Size 04 (4) │ │ │ │ -AE6F5 UID 00000000 (0) │ │ │ │ -AE6F9 GID Size 04 (4) │ │ │ │ -AE6FA GID 00000000 (0) │ │ │ │ -AE6FE PAYLOAD │ │ │ │ - │ │ │ │ -AFB5C LOCAL HEADER #91 04034B50 (67324752) │ │ │ │ -AFB60 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -AFB61 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -AFB62 General Purpose Flag 0000 (0) │ │ │ │ -AFB64 Compression Method 0000 (0) 'Stored' │ │ │ │ -AFB66 Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -AFB6A CRC 5E9E64F1 (1587438833) │ │ │ │ -AFB6E Compressed Size 000008EC (2284) │ │ │ │ -AFB72 Uncompressed Size 000008EC (2284) │ │ │ │ -AFB76 Filename Length 0016 (22) │ │ │ │ -AFB78 Extra Length 001C (28) │ │ │ │ -AFB7A Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xAFB7A: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -AFB90 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -AFB92 Length 0009 (9) │ │ │ │ -AFB94 Flags 03 (3) 'Modification Access' │ │ │ │ -AFB95 Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -AFB99 Access Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -AFB9D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -AFB9F Length 000B (11) │ │ │ │ -AFBA1 Version 01 (1) │ │ │ │ -AFBA2 UID Size 04 (4) │ │ │ │ -AFBA3 UID 00000000 (0) │ │ │ │ -AFBA7 GID Size 04 (4) │ │ │ │ -AFBA8 GID 00000000 (0) │ │ │ │ -AFBAC PAYLOAD │ │ │ │ - │ │ │ │ -B0498 LOCAL HEADER #92 04034B50 (67324752) │ │ │ │ -B049C Extract Zip Spec 0A (10) '1.0' │ │ │ │ -B049D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B049E General Purpose Flag 0000 (0) │ │ │ │ -B04A0 Compression Method 0000 (0) 'Stored' │ │ │ │ -B04A2 Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -B04A6 CRC 42E340AB (1122189483) │ │ │ │ -B04AA Compressed Size 00001F2E (7982) │ │ │ │ -B04AE Uncompressed Size 00001F2E (7982) │ │ │ │ -B04B2 Filename Length 001E (30) │ │ │ │ -B04B4 Extra Length 001C (28) │ │ │ │ -B04B6 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB04B6: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B04D4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B04D6 Length 0009 (9) │ │ │ │ -B04D8 Flags 03 (3) 'Modification Access' │ │ │ │ -B04D9 Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -B04DD Access Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -B04E1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B04E3 Length 000B (11) │ │ │ │ -B04E5 Version 01 (1) │ │ │ │ -B04E6 UID Size 04 (4) │ │ │ │ -B04E7 UID 00000000 (0) │ │ │ │ -B04EB GID Size 04 (4) │ │ │ │ -B04EC GID 00000000 (0) │ │ │ │ -B04F0 PAYLOAD │ │ │ │ - │ │ │ │ -B241E LOCAL HEADER #93 04034B50 (67324752) │ │ │ │ -B2422 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B2423 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B2424 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B2426 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B2428 Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -B242C CRC 02454CAB (38096043) │ │ │ │ -B2430 Compressed Size 00003D6D (15725) │ │ │ │ -B2434 Uncompressed Size 0001664F (91727) │ │ │ │ -B2438 Filename Length 001A (26) │ │ │ │ -B243A Extra Length 001C (28) │ │ │ │ -B243C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB243C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B2456 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B2458 Length 0009 (9) │ │ │ │ -B245A Flags 03 (3) 'Modification Access' │ │ │ │ -B245B Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -B245F Access Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -B2463 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B2465 Length 000B (11) │ │ │ │ -B2467 Version 01 (1) │ │ │ │ -B2468 UID Size 04 (4) │ │ │ │ -B2469 UID 00000000 (0) │ │ │ │ -B246D GID Size 04 (4) │ │ │ │ -B246E GID 00000000 (0) │ │ │ │ -B2472 PAYLOAD │ │ │ │ - │ │ │ │ -B61DF LOCAL HEADER #94 04034B50 (67324752) │ │ │ │ -B61E3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B61E4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B61E5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B61E7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B61E9 Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -B61ED CRC 3778562B (930633259) │ │ │ │ -B61F1 Compressed Size 000029CF (10703) │ │ │ │ -B61F5 Uncompressed Size 0000BB39 (47929) │ │ │ │ -B61F9 Filename Length 0018 (24) │ │ │ │ -B61FB Extra Length 001C (28) │ │ │ │ -B61FD Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB61FD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B6215 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B6217 Length 0009 (9) │ │ │ │ -B6219 Flags 03 (3) 'Modification Access' │ │ │ │ -B621A Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -B621E Access Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -B6222 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B6224 Length 000B (11) │ │ │ │ -B6226 Version 01 (1) │ │ │ │ -B6227 UID Size 04 (4) │ │ │ │ -B6228 UID 00000000 (0) │ │ │ │ -B622C GID Size 04 (4) │ │ │ │ -B622D GID 00000000 (0) │ │ │ │ -B6231 PAYLOAD │ │ │ │ - │ │ │ │ -B8C00 LOCAL HEADER #95 04034B50 (67324752) │ │ │ │ -B8C04 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8C05 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8C06 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8C08 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8C0A Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -B8C0E CRC DCB3B516 (3702764822) │ │ │ │ -B8C12 Compressed Size 000000AE (174) │ │ │ │ -B8C16 Uncompressed Size 000000FC (252) │ │ │ │ -B8C1A Filename Length 0016 (22) │ │ │ │ -B8C1C Extra Length 001C (28) │ │ │ │ -B8C1E Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8C1E: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8C34 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8C36 Length 0009 (9) │ │ │ │ -B8C38 Flags 03 (3) 'Modification Access' │ │ │ │ -B8C39 Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -B8C3D Access Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -B8C41 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8C43 Length 000B (11) │ │ │ │ -B8C45 Version 01 (1) │ │ │ │ -B8C46 UID Size 04 (4) │ │ │ │ -B8C47 UID 00000000 (0) │ │ │ │ -B8C4B GID Size 04 (4) │ │ │ │ -B8C4C GID 00000000 (0) │ │ │ │ -B8C50 PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ +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 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +08108 CRC 93C4F0D6 (2479157462) │ │ │ │ +0810C Compressed Size 00000E65 (3685) │ │ │ │ +08110 Uncompressed Size 00003092 (12434) │ │ │ │ +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 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +0813E Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 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 │ │ │ │ + │ │ │ │ +08FB6 LOCAL HEADER #10 04034B50 (67324752) │ │ │ │ +08FBA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +08FBB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +08FBC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +08FBE Compression Method 0008 (8) 'Deflated' │ │ │ │ +08FC0 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +08FC4 CRC 4F204BB6 (1327516598) │ │ │ │ +08FC8 Compressed Size 00000994 (2452) │ │ │ │ +08FCC Uncompressed Size 00001D3D (7485) │ │ │ │ +08FD0 Filename Length 0019 (25) │ │ │ │ +08FD2 Extra Length 001C (28) │ │ │ │ +08FD4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8FD4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +08FED Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +08FEF Length 0009 (9) │ │ │ │ +08FF1 Flags 03 (3) 'Modification Access' │ │ │ │ +08FF2 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +08FF6 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +08FFA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +08FFC Length 000B (11) │ │ │ │ +08FFE Version 01 (1) │ │ │ │ +08FFF UID Size 04 (4) │ │ │ │ +09000 UID 00000000 (0) │ │ │ │ +09004 GID Size 04 (4) │ │ │ │ +09005 GID 00000000 (0) │ │ │ │ +09009 PAYLOAD │ │ │ │ + │ │ │ │ +0999D LOCAL HEADER #11 04034B50 (67324752) │ │ │ │ +099A1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +099A2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +099A3 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +099A5 Compression Method 0008 (8) 'Deflated' │ │ │ │ +099A7 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +099AB CRC CA8CA5BA (3398215098) │ │ │ │ +099AF Compressed Size 0000387F (14463) │ │ │ │ +099B3 Uncompressed Size 0000F7F4 (63476) │ │ │ │ +099B7 Filename Length 0015 (21) │ │ │ │ +099B9 Extra Length 001C (28) │ │ │ │ +099BB Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x99BB: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +099D0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +099D2 Length 0009 (9) │ │ │ │ +099D4 Flags 03 (3) 'Modification Access' │ │ │ │ +099D5 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +099D9 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +099DD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +099DF Length 000B (11) │ │ │ │ +099E1 Version 01 (1) │ │ │ │ +099E2 UID Size 04 (4) │ │ │ │ +099E3 UID 00000000 (0) │ │ │ │ +099E7 GID Size 04 (4) │ │ │ │ +099E8 GID 00000000 (0) │ │ │ │ +099EC PAYLOAD │ │ │ │ + │ │ │ │ +0D26B LOCAL HEADER #12 04034B50 (67324752) │ │ │ │ +0D26F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +0D270 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +0D271 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +0D273 Compression Method 0008 (8) 'Deflated' │ │ │ │ +0D275 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +0D279 CRC AFDB078C (2950367116) │ │ │ │ +0D27D Compressed Size 0000AB09 (43785) │ │ │ │ +0D281 Uncompressed Size 0003E0D3 (254163) │ │ │ │ +0D285 Filename Length 0012 (18) │ │ │ │ +0D287 Extra Length 001C (28) │ │ │ │ +0D289 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xD289: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +0D29B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +0D29D Length 0009 (9) │ │ │ │ +0D29F Flags 03 (3) 'Modification Access' │ │ │ │ +0D2A0 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +0D2A4 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +0D2A8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +0D2AA Length 000B (11) │ │ │ │ +0D2AC Version 01 (1) │ │ │ │ +0D2AD UID Size 04 (4) │ │ │ │ +0D2AE UID 00000000 (0) │ │ │ │ +0D2B2 GID Size 04 (4) │ │ │ │ +0D2B3 GID 00000000 (0) │ │ │ │ +0D2B7 PAYLOAD │ │ │ │ + │ │ │ │ +17DC0 LOCAL HEADER #13 04034B50 (67324752) │ │ │ │ +17DC4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +17DC5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +17DC6 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +17DC8 Compression Method 0008 (8) 'Deflated' │ │ │ │ +17DCA Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +17DCE CRC 32245BB0 (841243568) │ │ │ │ +17DD2 Compressed Size 00003B10 (15120) │ │ │ │ +17DD6 Uncompressed Size 0001B46C (111724) │ │ │ │ +17DDA Filename Length 0015 (21) │ │ │ │ +17DDC Extra Length 001C (28) │ │ │ │ +17DDE Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x17DDE: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +17DF3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +17DF5 Length 0009 (9) │ │ │ │ +17DF7 Flags 03 (3) 'Modification Access' │ │ │ │ +17DF8 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +17DFC Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +17E00 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +17E02 Length 000B (11) │ │ │ │ +17E04 Version 01 (1) │ │ │ │ +17E05 UID Size 04 (4) │ │ │ │ +17E06 UID 00000000 (0) │ │ │ │ +17E0A GID Size 04 (4) │ │ │ │ +17E0B GID 00000000 (0) │ │ │ │ +17E0F PAYLOAD │ │ │ │ + │ │ │ │ +1B91F LOCAL HEADER #14 04034B50 (67324752) │ │ │ │ +1B923 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +1B924 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +1B925 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +1B927 Compression Method 0008 (8) 'Deflated' │ │ │ │ +1B929 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +1B92D CRC DD758B13 (3715468051) │ │ │ │ +1B931 Compressed Size 000091A1 (37281) │ │ │ │ +1B935 Uncompressed Size 0003D5E9 (251369) │ │ │ │ +1B939 Filename Length 0014 (20) │ │ │ │ +1B93B Extra Length 001C (28) │ │ │ │ +1B93D Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x1B93D: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +1B951 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +1B953 Length 0009 (9) │ │ │ │ +1B955 Flags 03 (3) 'Modification Access' │ │ │ │ +1B956 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +1B95A Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +1B95E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +1B960 Length 000B (11) │ │ │ │ +1B962 Version 01 (1) │ │ │ │ +1B963 UID Size 04 (4) │ │ │ │ +1B964 UID 00000000 (0) │ │ │ │ +1B968 GID Size 04 (4) │ │ │ │ +1B969 GID 00000000 (0) │ │ │ │ +1B96D PAYLOAD │ │ │ │ + │ │ │ │ +24B0E LOCAL HEADER #15 04034B50 (67324752) │ │ │ │ +24B12 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +24B13 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +24B14 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +24B16 Compression Method 0008 (8) 'Deflated' │ │ │ │ +24B18 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +24B1C CRC 9D665911 (2640730385) │ │ │ │ +24B20 Compressed Size 00001219 (4633) │ │ │ │ +24B24 Uncompressed Size 00003C91 (15505) │ │ │ │ +24B28 Filename Length 0010 (16) │ │ │ │ +24B2A Extra Length 001C (28) │ │ │ │ +24B2C Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x24B2C: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +24B3C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +24B3E Length 0009 (9) │ │ │ │ +24B40 Flags 03 (3) 'Modification Access' │ │ │ │ +24B41 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +24B45 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +24B49 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +24B4B Length 000B (11) │ │ │ │ +24B4D Version 01 (1) │ │ │ │ +24B4E UID Size 04 (4) │ │ │ │ +24B4F UID 00000000 (0) │ │ │ │ +24B53 GID Size 04 (4) │ │ │ │ +24B54 GID 00000000 (0) │ │ │ │ +24B58 PAYLOAD │ │ │ │ + │ │ │ │ +25D71 LOCAL HEADER #16 04034B50 (67324752) │ │ │ │ +25D75 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +25D76 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +25D77 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +25D79 Compression Method 0008 (8) 'Deflated' │ │ │ │ +25D7B Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +25D7F CRC 56720776 (1450313590) │ │ │ │ +25D83 Compressed Size 00002A63 (10851) │ │ │ │ +25D87 Uncompressed Size 0001151F (70943) │ │ │ │ +25D8B Filename Length 0016 (22) │ │ │ │ +25D8D Extra Length 001C (28) │ │ │ │ +25D8F Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x25D8F: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +25DA5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +25DA7 Length 0009 (9) │ │ │ │ +25DA9 Flags 03 (3) 'Modification Access' │ │ │ │ +25DAA Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +25DAE Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +25DB2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +25DB4 Length 000B (11) │ │ │ │ +25DB6 Version 01 (1) │ │ │ │ +25DB7 UID Size 04 (4) │ │ │ │ +25DB8 UID 00000000 (0) │ │ │ │ +25DBC GID Size 04 (4) │ │ │ │ +25DBD GID 00000000 (0) │ │ │ │ +25DC1 PAYLOAD │ │ │ │ + │ │ │ │ +28824 LOCAL HEADER #17 04034B50 (67324752) │ │ │ │ +28828 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +28829 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +2882A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +2882C Compression Method 0008 (8) 'Deflated' │ │ │ │ +2882E Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +28832 CRC 8CD1C33D (2362557245) │ │ │ │ +28836 Compressed Size 000014DA (5338) │ │ │ │ +2883A Uncompressed Size 0000518D (20877) │ │ │ │ +2883E Filename Length 001D (29) │ │ │ │ +28840 Extra Length 001C (28) │ │ │ │ +28842 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x28842: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +2885F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +28861 Length 0009 (9) │ │ │ │ +28863 Flags 03 (3) 'Modification Access' │ │ │ │ +28864 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +28868 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +2886C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +2886E Length 000B (11) │ │ │ │ +28870 Version 01 (1) │ │ │ │ +28871 UID Size 04 (4) │ │ │ │ +28872 UID 00000000 (0) │ │ │ │ +28876 GID Size 04 (4) │ │ │ │ +28877 GID 00000000 (0) │ │ │ │ +2887B PAYLOAD │ │ │ │ + │ │ │ │ +29D55 LOCAL HEADER #18 04034B50 (67324752) │ │ │ │ +29D59 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +29D5A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +29D5B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +29D5D Compression Method 0008 (8) 'Deflated' │ │ │ │ +29D5F Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +29D63 CRC AD8C2B6A (2911644522) │ │ │ │ +29D67 Compressed Size 0000380C (14348) │ │ │ │ +29D6B Uncompressed Size 0000EA4C (59980) │ │ │ │ +29D6F Filename Length 001C (28) │ │ │ │ +29D71 Extra Length 001C (28) │ │ │ │ +29D73 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x29D73: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +29D8F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +29D91 Length 0009 (9) │ │ │ │ +29D93 Flags 03 (3) 'Modification Access' │ │ │ │ +29D94 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +29D98 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +29D9C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +29D9E Length 000B (11) │ │ │ │ +29DA0 Version 01 (1) │ │ │ │ +29DA1 UID Size 04 (4) │ │ │ │ +29DA2 UID 00000000 (0) │ │ │ │ +29DA6 GID Size 04 (4) │ │ │ │ +29DA7 GID 00000000 (0) │ │ │ │ +29DAB PAYLOAD │ │ │ │ + │ │ │ │ +2D5B7 LOCAL HEADER #19 04034B50 (67324752) │ │ │ │ +2D5BB Extract Zip Spec 14 (20) '2.0' │ │ │ │ +2D5BC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +2D5BD General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +2D5BF Compression Method 0008 (8) 'Deflated' │ │ │ │ +2D5C1 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +2D5C5 CRC 2E09C12B (772391211) │ │ │ │ +2D5C9 Compressed Size 000006A2 (1698) │ │ │ │ +2D5CD Uncompressed Size 000011F4 (4596) │ │ │ │ +2D5D1 Filename Length 001C (28) │ │ │ │ +2D5D3 Extra Length 001C (28) │ │ │ │ +2D5D5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x2D5D5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +2D5F1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +2D5F3 Length 0009 (9) │ │ │ │ +2D5F5 Flags 03 (3) 'Modification Access' │ │ │ │ +2D5F6 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +2D5FA Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +2D5FE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +2D600 Length 000B (11) │ │ │ │ +2D602 Version 01 (1) │ │ │ │ +2D603 UID Size 04 (4) │ │ │ │ +2D604 UID 00000000 (0) │ │ │ │ +2D608 GID Size 04 (4) │ │ │ │ +2D609 GID 00000000 (0) │ │ │ │ +2D60D PAYLOAD │ │ │ │ + │ │ │ │ +2DCAF LOCAL HEADER #20 04034B50 (67324752) │ │ │ │ +2DCB3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +2DCB4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +2DCB5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +2DCB7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +2DCB9 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +2DCBD CRC BFC83E05 (3217571333) │ │ │ │ +2DCC1 Compressed Size 00001082 (4226) │ │ │ │ +2DCC5 Uncompressed Size 00004BFF (19455) │ │ │ │ +2DCC9 Filename Length 001B (27) │ │ │ │ +2DCCB Extra Length 001C (28) │ │ │ │ +2DCCD Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x2DCCD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +2DCE8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +2DCEA Length 0009 (9) │ │ │ │ +2DCEC Flags 03 (3) 'Modification Access' │ │ │ │ +2DCED Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +2DCF1 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +2DCF5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +2DCF7 Length 000B (11) │ │ │ │ +2DCF9 Version 01 (1) │ │ │ │ +2DCFA UID Size 04 (4) │ │ │ │ +2DCFB UID 00000000 (0) │ │ │ │ +2DCFF GID Size 04 (4) │ │ │ │ +2DD00 GID 00000000 (0) │ │ │ │ +2DD04 PAYLOAD │ │ │ │ + │ │ │ │ +2ED86 LOCAL HEADER #21 04034B50 (67324752) │ │ │ │ +2ED8A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +2ED8B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +2ED8C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +2ED8E Compression Method 0008 (8) 'Deflated' │ │ │ │ +2ED90 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +2ED94 CRC 5F7510E3 (1601507555) │ │ │ │ +2ED98 Compressed Size 00003BDB (15323) │ │ │ │ +2ED9C Uncompressed Size 0000D783 (55171) │ │ │ │ +2EDA0 Filename Length 001D (29) │ │ │ │ +2EDA2 Extra Length 001C (28) │ │ │ │ +2EDA4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x2EDA4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +2EDC1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +2EDC3 Length 0009 (9) │ │ │ │ +2EDC5 Flags 03 (3) 'Modification Access' │ │ │ │ +2EDC6 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +2EDCA Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +2EDCE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +2EDD0 Length 000B (11) │ │ │ │ +2EDD2 Version 01 (1) │ │ │ │ +2EDD3 UID Size 04 (4) │ │ │ │ +2EDD4 UID 00000000 (0) │ │ │ │ +2EDD8 GID Size 04 (4) │ │ │ │ +2EDD9 GID 00000000 (0) │ │ │ │ +2EDDD PAYLOAD │ │ │ │ + │ │ │ │ +329B8 LOCAL HEADER #22 04034B50 (67324752) │ │ │ │ +329BC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +329BD Extract OS 00 (0) 'MS-DOS' │ │ │ │ +329BE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +329C0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +329C2 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +329C6 CRC 8217C9B8 (2182597048) │ │ │ │ +329CA Compressed Size 00000D6B (3435) │ │ │ │ +329CE Uncompressed Size 0000388D (14477) │ │ │ │ +329D2 Filename Length 001D (29) │ │ │ │ +329D4 Extra Length 001C (28) │ │ │ │ +329D6 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x329D6: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +329F3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +329F5 Length 0009 (9) │ │ │ │ +329F7 Flags 03 (3) 'Modification Access' │ │ │ │ +329F8 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +329FC Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +32A00 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +32A02 Length 000B (11) │ │ │ │ +32A04 Version 01 (1) │ │ │ │ +32A05 UID Size 04 (4) │ │ │ │ +32A06 UID 00000000 (0) │ │ │ │ +32A0A GID Size 04 (4) │ │ │ │ +32A0B GID 00000000 (0) │ │ │ │ +32A0F PAYLOAD │ │ │ │ + │ │ │ │ +3377A LOCAL HEADER #23 04034B50 (67324752) │ │ │ │ +3377E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3377F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +33780 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +33782 Compression Method 0008 (8) 'Deflated' │ │ │ │ +33784 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +33788 CRC AD4FDB1F (2907691807) │ │ │ │ +3378C Compressed Size 00001C69 (7273) │ │ │ │ +33790 Uncompressed Size 0000C186 (49542) │ │ │ │ +33794 Filename Length 001A (26) │ │ │ │ +33796 Extra Length 001C (28) │ │ │ │ +33798 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x33798: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +337B2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +337B4 Length 0009 (9) │ │ │ │ +337B6 Flags 03 (3) 'Modification Access' │ │ │ │ +337B7 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +337BB Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +337BF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +337C1 Length 000B (11) │ │ │ │ +337C3 Version 01 (1) │ │ │ │ +337C4 UID Size 04 (4) │ │ │ │ +337C5 UID 00000000 (0) │ │ │ │ +337C9 GID Size 04 (4) │ │ │ │ +337CA GID 00000000 (0) │ │ │ │ +337CE PAYLOAD │ │ │ │ + │ │ │ │ +35437 LOCAL HEADER #24 04034B50 (67324752) │ │ │ │ +3543B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3543C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3543D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3543F Compression Method 0008 (8) 'Deflated' │ │ │ │ +35441 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +35445 CRC 04F1FDC5 (82968005) │ │ │ │ +35449 Compressed Size 000003DF (991) │ │ │ │ +3544D Uncompressed Size 00000935 (2357) │ │ │ │ +35451 Filename Length 0012 (18) │ │ │ │ +35453 Extra Length 001C (28) │ │ │ │ +35455 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x35455: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +35467 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +35469 Length 0009 (9) │ │ │ │ +3546B Flags 03 (3) 'Modification Access' │ │ │ │ +3546C Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +35470 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +35474 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +35476 Length 000B (11) │ │ │ │ +35478 Version 01 (1) │ │ │ │ +35479 UID Size 04 (4) │ │ │ │ +3547A UID 00000000 (0) │ │ │ │ +3547E GID Size 04 (4) │ │ │ │ +3547F GID 00000000 (0) │ │ │ │ +35483 PAYLOAD │ │ │ │ + │ │ │ │ +35862 LOCAL HEADER #25 04034B50 (67324752) │ │ │ │ +35866 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +35867 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +35868 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3586A Compression Method 0008 (8) 'Deflated' │ │ │ │ +3586C Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +35870 CRC C605FB96 (3322280854) │ │ │ │ +35874 Compressed Size 000001D3 (467) │ │ │ │ +35878 Uncompressed Size 00000311 (785) │ │ │ │ +3587C Filename Length 0020 (32) │ │ │ │ +3587E Extra Length 001C (28) │ │ │ │ +35880 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x35880: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +358A0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +358A2 Length 0009 (9) │ │ │ │ +358A4 Flags 03 (3) 'Modification Access' │ │ │ │ +358A5 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +358A9 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +358AD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +358AF Length 000B (11) │ │ │ │ +358B1 Version 01 (1) │ │ │ │ +358B2 UID Size 04 (4) │ │ │ │ +358B3 UID 00000000 (0) │ │ │ │ +358B7 GID Size 04 (4) │ │ │ │ +358B8 GID 00000000 (0) │ │ │ │ +358BC PAYLOAD │ │ │ │ + │ │ │ │ +35A8F LOCAL HEADER #26 04034B50 (67324752) │ │ │ │ +35A93 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +35A94 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +35A95 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +35A97 Compression Method 0008 (8) 'Deflated' │ │ │ │ +35A99 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +35A9D CRC BBDF5426 (3151975462) │ │ │ │ +35AA1 Compressed Size 000017AB (6059) │ │ │ │ +35AA5 Uncompressed Size 00009D18 (40216) │ │ │ │ +35AA9 Filename Length 001B (27) │ │ │ │ +35AAB Extra Length 001C (28) │ │ │ │ +35AAD Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x35AAD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +35AC8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +35ACA Length 0009 (9) │ │ │ │ +35ACC Flags 03 (3) 'Modification Access' │ │ │ │ +35ACD Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +35AD1 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +35AD5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +35AD7 Length 000B (11) │ │ │ │ +35AD9 Version 01 (1) │ │ │ │ +35ADA UID Size 04 (4) │ │ │ │ +35ADB UID 00000000 (0) │ │ │ │ +35ADF GID Size 04 (4) │ │ │ │ +35AE0 GID 00000000 (0) │ │ │ │ +35AE4 PAYLOAD │ │ │ │ + │ │ │ │ +3728F LOCAL HEADER #27 04034B50 (67324752) │ │ │ │ +37293 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +37294 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +37295 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +37297 Compression Method 0008 (8) 'Deflated' │ │ │ │ +37299 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +3729D CRC D6E9E108 (3605651720) │ │ │ │ +372A1 Compressed Size 00001371 (4977) │ │ │ │ +372A5 Uncompressed Size 00003B61 (15201) │ │ │ │ +372A9 Filename Length 0015 (21) │ │ │ │ +372AB Extra Length 001C (28) │ │ │ │ +372AD Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x372AD: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +372C2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +372C4 Length 0009 (9) │ │ │ │ +372C6 Flags 03 (3) 'Modification Access' │ │ │ │ +372C7 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +372CB Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +372CF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +372D1 Length 000B (11) │ │ │ │ +372D3 Version 01 (1) │ │ │ │ +372D4 UID Size 04 (4) │ │ │ │ +372D5 UID 00000000 (0) │ │ │ │ +372D9 GID Size 04 (4) │ │ │ │ +372DA GID 00000000 (0) │ │ │ │ +372DE PAYLOAD │ │ │ │ + │ │ │ │ +3864F LOCAL HEADER #28 04034B50 (67324752) │ │ │ │ +38653 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +38654 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +38655 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +38657 Compression Method 0008 (8) 'Deflated' │ │ │ │ +38659 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +3865D CRC AD15BDB2 (2903883186) │ │ │ │ +38661 Compressed Size 00000AD0 (2768) │ │ │ │ +38665 Uncompressed Size 00002135 (8501) │ │ │ │ +38669 Filename Length 0011 (17) │ │ │ │ +3866B Extra Length 001C (28) │ │ │ │ +3866D Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3866D: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3867E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +38680 Length 0009 (9) │ │ │ │ +38682 Flags 03 (3) 'Modification Access' │ │ │ │ +38683 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +38687 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +3868B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3868D Length 000B (11) │ │ │ │ +3868F Version 01 (1) │ │ │ │ +38690 UID Size 04 (4) │ │ │ │ +38691 UID 00000000 (0) │ │ │ │ +38695 GID Size 04 (4) │ │ │ │ +38696 GID 00000000 (0) │ │ │ │ +3869A PAYLOAD │ │ │ │ + │ │ │ │ +3916A LOCAL HEADER #29 04034B50 (67324752) │ │ │ │ +3916E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3916F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +39170 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +39172 Compression Method 0008 (8) 'Deflated' │ │ │ │ +39174 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +39178 CRC 30C3E93A (818145594) │ │ │ │ +3917C Compressed Size 000003FE (1022) │ │ │ │ +39180 Uncompressed Size 00000F0C (3852) │ │ │ │ +39184 Filename Length 0014 (20) │ │ │ │ +39186 Extra Length 001C (28) │ │ │ │ +39188 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x39188: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3919C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3919E Length 0009 (9) │ │ │ │ +391A0 Flags 03 (3) 'Modification Access' │ │ │ │ +391A1 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +391A5 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +391A9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +391AB Length 000B (11) │ │ │ │ +391AD Version 01 (1) │ │ │ │ +391AE UID Size 04 (4) │ │ │ │ +391AF UID 00000000 (0) │ │ │ │ +391B3 GID Size 04 (4) │ │ │ │ +391B4 GID 00000000 (0) │ │ │ │ +391B8 PAYLOAD │ │ │ │ + │ │ │ │ +395B6 LOCAL HEADER #30 04034B50 (67324752) │ │ │ │ +395BA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +395BB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +395BC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +395BE Compression Method 0008 (8) 'Deflated' │ │ │ │ +395C0 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +395C4 CRC F6397314 (4130960148) │ │ │ │ +395C8 Compressed Size 00001260 (4704) │ │ │ │ +395CC Uncompressed Size 0000346B (13419) │ │ │ │ +395D0 Filename Length 0014 (20) │ │ │ │ +395D2 Extra Length 001C (28) │ │ │ │ +395D4 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x395D4: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +395E8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +395EA Length 0009 (9) │ │ │ │ +395EC Flags 03 (3) 'Modification Access' │ │ │ │ +395ED Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +395F1 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +395F5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +395F7 Length 000B (11) │ │ │ │ +395F9 Version 01 (1) │ │ │ │ +395FA UID Size 04 (4) │ │ │ │ +395FB UID 00000000 (0) │ │ │ │ +395FF GID Size 04 (4) │ │ │ │ +39600 GID 00000000 (0) │ │ │ │ +39604 PAYLOAD │ │ │ │ + │ │ │ │ +3A864 LOCAL HEADER #31 04034B50 (67324752) │ │ │ │ +3A868 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3A869 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3A86A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3A86C Compression Method 0008 (8) 'Deflated' │ │ │ │ +3A86E Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +3A872 CRC F699BBFB (4137270267) │ │ │ │ +3A876 Compressed Size 00000ACF (2767) │ │ │ │ +3A87A Uncompressed Size 000022FF (8959) │ │ │ │ +3A87E Filename Length 001B (27) │ │ │ │ +3A880 Extra Length 001C (28) │ │ │ │ +3A882 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3A882: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3A89D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3A89F Length 0009 (9) │ │ │ │ +3A8A1 Flags 03 (3) 'Modification Access' │ │ │ │ +3A8A2 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +3A8A6 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +3A8AA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3A8AC Length 000B (11) │ │ │ │ +3A8AE Version 01 (1) │ │ │ │ +3A8AF UID Size 04 (4) │ │ │ │ +3A8B0 UID 00000000 (0) │ │ │ │ +3A8B4 GID Size 04 (4) │ │ │ │ +3A8B5 GID 00000000 (0) │ │ │ │ +3A8B9 PAYLOAD │ │ │ │ + │ │ │ │ +3B388 LOCAL HEADER #32 04034B50 (67324752) │ │ │ │ +3B38C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3B38D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3B38E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3B390 Compression Method 0008 (8) 'Deflated' │ │ │ │ +3B392 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +3B396 CRC C8DC1D85 (3369868677) │ │ │ │ +3B39A Compressed Size 00000C51 (3153) │ │ │ │ +3B39E Uncompressed Size 0000273C (10044) │ │ │ │ +3B3A2 Filename Length 0013 (19) │ │ │ │ +3B3A4 Extra Length 001C (28) │ │ │ │ +3B3A6 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3B3A6: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3B3B9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3B3BB Length 0009 (9) │ │ │ │ +3B3BD Flags 03 (3) 'Modification Access' │ │ │ │ +3B3BE Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +3B3C2 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +3B3C6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3B3C8 Length 000B (11) │ │ │ │ +3B3CA Version 01 (1) │ │ │ │ +3B3CB UID Size 04 (4) │ │ │ │ +3B3CC UID 00000000 (0) │ │ │ │ +3B3D0 GID Size 04 (4) │ │ │ │ +3B3D1 GID 00000000 (0) │ │ │ │ +3B3D5 PAYLOAD │ │ │ │ + │ │ │ │ +3C026 LOCAL HEADER #33 04034B50 (67324752) │ │ │ │ +3C02A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3C02B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3C02C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3C02E Compression Method 0008 (8) 'Deflated' │ │ │ │ +3C030 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +3C034 CRC 3E3BCF44 (1044107076) │ │ │ │ +3C038 Compressed Size 00000C92 (3218) │ │ │ │ +3C03C Uncompressed Size 00003D10 (15632) │ │ │ │ +3C040 Filename Length 0014 (20) │ │ │ │ +3C042 Extra Length 001C (28) │ │ │ │ +3C044 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3C044: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3C058 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3C05A Length 0009 (9) │ │ │ │ +3C05C Flags 03 (3) 'Modification Access' │ │ │ │ +3C05D Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +3C061 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +3C065 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3C067 Length 000B (11) │ │ │ │ +3C069 Version 01 (1) │ │ │ │ +3C06A UID Size 04 (4) │ │ │ │ +3C06B UID 00000000 (0) │ │ │ │ +3C06F GID Size 04 (4) │ │ │ │ +3C070 GID 00000000 (0) │ │ │ │ +3C074 PAYLOAD │ │ │ │ + │ │ │ │ +3CD06 LOCAL HEADER #34 04034B50 (67324752) │ │ │ │ +3CD0A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3CD0B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3CD0C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3CD0E Compression Method 0008 (8) 'Deflated' │ │ │ │ +3CD10 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +3CD14 CRC 6812E40F (1746068495) │ │ │ │ +3CD18 Compressed Size 00000F45 (3909) │ │ │ │ +3CD1C Uncompressed Size 00003744 (14148) │ │ │ │ +3CD20 Filename Length 000F (15) │ │ │ │ +3CD22 Extra Length 001C (28) │ │ │ │ +3CD24 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3CD24: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3CD33 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3CD35 Length 0009 (9) │ │ │ │ +3CD37 Flags 03 (3) 'Modification Access' │ │ │ │ +3CD38 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +3CD3C Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +3CD40 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3CD42 Length 000B (11) │ │ │ │ +3CD44 Version 01 (1) │ │ │ │ +3CD45 UID Size 04 (4) │ │ │ │ +3CD46 UID 00000000 (0) │ │ │ │ +3CD4A GID Size 04 (4) │ │ │ │ +3CD4B GID 00000000 (0) │ │ │ │ +3CD4F PAYLOAD │ │ │ │ + │ │ │ │ +3DC94 LOCAL HEADER #35 04034B50 (67324752) │ │ │ │ +3DC98 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3DC99 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3DC9A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3DC9C Compression Method 0008 (8) 'Deflated' │ │ │ │ +3DC9E Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +3DCA2 CRC 1FC86902 (533227778) │ │ │ │ +3DCA6 Compressed Size 000006BA (1722) │ │ │ │ +3DCAA Uncompressed Size 00001A79 (6777) │ │ │ │ +3DCAE Filename Length 000F (15) │ │ │ │ +3DCB0 Extra Length 001C (28) │ │ │ │ +3DCB2 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3DCB2: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3DCC1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3DCC3 Length 0009 (9) │ │ │ │ +3DCC5 Flags 03 (3) 'Modification Access' │ │ │ │ +3DCC6 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +3DCCA Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +3DCCE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3DCD0 Length 000B (11) │ │ │ │ +3DCD2 Version 01 (1) │ │ │ │ +3DCD3 UID Size 04 (4) │ │ │ │ +3DCD4 UID 00000000 (0) │ │ │ │ +3DCD8 GID Size 04 (4) │ │ │ │ +3DCD9 GID 00000000 (0) │ │ │ │ +3DCDD PAYLOAD │ │ │ │ + │ │ │ │ +3E397 LOCAL HEADER #36 04034B50 (67324752) │ │ │ │ +3E39B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3E39C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3E39D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3E39F Compression Method 0008 (8) 'Deflated' │ │ │ │ +3E3A1 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +3E3A5 CRC D3195915 (3541653781) │ │ │ │ +3E3A9 Compressed Size 00001A45 (6725) │ │ │ │ +3E3AD Uncompressed Size 000064FA (25850) │ │ │ │ +3E3B1 Filename Length 0013 (19) │ │ │ │ +3E3B3 Extra Length 001C (28) │ │ │ │ +3E3B5 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3E3B5: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3E3C8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3E3CA Length 0009 (9) │ │ │ │ +3E3CC Flags 03 (3) 'Modification Access' │ │ │ │ +3E3CD Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +3E3D1 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +3E3D5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3E3D7 Length 000B (11) │ │ │ │ +3E3D9 Version 01 (1) │ │ │ │ +3E3DA UID Size 04 (4) │ │ │ │ +3E3DB UID 00000000 (0) │ │ │ │ +3E3DF GID Size 04 (4) │ │ │ │ +3E3E0 GID 00000000 (0) │ │ │ │ +3E3E4 PAYLOAD │ │ │ │ + │ │ │ │ +3FE29 LOCAL HEADER #37 04034B50 (67324752) │ │ │ │ +3FE2D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3FE2E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3FE2F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3FE31 Compression Method 0008 (8) 'Deflated' │ │ │ │ +3FE33 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +3FE37 CRC 8BADFA46 (2343434822) │ │ │ │ +3FE3B Compressed Size 000009A5 (2469) │ │ │ │ +3FE3F Uncompressed Size 00001B64 (7012) │ │ │ │ +3FE43 Filename Length 0010 (16) │ │ │ │ +3FE45 Extra Length 001C (28) │ │ │ │ +3FE47 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3FE47: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3FE57 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3FE59 Length 0009 (9) │ │ │ │ +3FE5B Flags 03 (3) 'Modification Access' │ │ │ │ +3FE5C Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +3FE60 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +3FE64 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3FE66 Length 000B (11) │ │ │ │ +3FE68 Version 01 (1) │ │ │ │ +3FE69 UID Size 04 (4) │ │ │ │ +3FE6A UID 00000000 (0) │ │ │ │ +3FE6E GID Size 04 (4) │ │ │ │ +3FE6F GID 00000000 (0) │ │ │ │ +3FE73 PAYLOAD │ │ │ │ + │ │ │ │ +40818 LOCAL HEADER #38 04034B50 (67324752) │ │ │ │ +4081C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4081D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4081E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +40820 Compression Method 0008 (8) 'Deflated' │ │ │ │ +40822 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +40826 CRC 5E895E09 (1586060809) │ │ │ │ +4082A Compressed Size 000006B6 (1718) │ │ │ │ +4082E Uncompressed Size 00001565 (5477) │ │ │ │ +40832 Filename Length 0012 (18) │ │ │ │ +40834 Extra Length 001C (28) │ │ │ │ +40836 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x40836: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +40848 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +4084A Length 0009 (9) │ │ │ │ +4084C Flags 03 (3) 'Modification Access' │ │ │ │ +4084D Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +40851 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +40855 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +40857 Length 000B (11) │ │ │ │ +40859 Version 01 (1) │ │ │ │ +4085A UID Size 04 (4) │ │ │ │ +4085B UID 00000000 (0) │ │ │ │ +4085F GID Size 04 (4) │ │ │ │ +40860 GID 00000000 (0) │ │ │ │ +40864 PAYLOAD │ │ │ │ + │ │ │ │ +40F1A LOCAL HEADER #39 04034B50 (67324752) │ │ │ │ +40F1E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +40F1F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +40F20 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +40F22 Compression Method 0008 (8) 'Deflated' │ │ │ │ +40F24 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +40F28 CRC C45D7864 (3294460004) │ │ │ │ +40F2C Compressed Size 00002D5E (11614) │ │ │ │ +40F30 Uncompressed Size 0000D07E (53374) │ │ │ │ +40F34 Filename Length 0010 (16) │ │ │ │ +40F36 Extra Length 001C (28) │ │ │ │ +40F38 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x40F38: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +40F48 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +40F4A Length 0009 (9) │ │ │ │ +40F4C Flags 03 (3) 'Modification Access' │ │ │ │ +40F4D Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +40F51 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +40F55 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +40F57 Length 000B (11) │ │ │ │ +40F59 Version 01 (1) │ │ │ │ +40F5A UID Size 04 (4) │ │ │ │ +40F5B UID 00000000 (0) │ │ │ │ +40F5F GID Size 04 (4) │ │ │ │ +40F60 GID 00000000 (0) │ │ │ │ +40F64 PAYLOAD │ │ │ │ + │ │ │ │ +43CC2 LOCAL HEADER #40 04034B50 (67324752) │ │ │ │ +43CC6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +43CC7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +43CC8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +43CCA Compression Method 0008 (8) 'Deflated' │ │ │ │ +43CCC Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +43CD0 CRC 8F7E3CC4 (2407414980) │ │ │ │ +43CD4 Compressed Size 00001E83 (7811) │ │ │ │ +43CD8 Uncompressed Size 00009AAA (39594) │ │ │ │ +43CDC Filename Length 0012 (18) │ │ │ │ +43CDE Extra Length 001C (28) │ │ │ │ +43CE0 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x43CE0: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +43CF2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +43CF4 Length 0009 (9) │ │ │ │ +43CF6 Flags 03 (3) 'Modification Access' │ │ │ │ +43CF7 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +43CFB Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +43CFF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +43D01 Length 000B (11) │ │ │ │ +43D03 Version 01 (1) │ │ │ │ +43D04 UID Size 04 (4) │ │ │ │ +43D05 UID 00000000 (0) │ │ │ │ +43D09 GID Size 04 (4) │ │ │ │ +43D0A GID 00000000 (0) │ │ │ │ +43D0E PAYLOAD │ │ │ │ + │ │ │ │ +45B91 LOCAL HEADER #41 04034B50 (67324752) │ │ │ │ +45B95 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +45B96 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +45B97 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +45B99 Compression Method 0008 (8) 'Deflated' │ │ │ │ +45B9B Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +45B9F CRC 77D28BC2 (2010287042) │ │ │ │ +45BA3 Compressed Size 0000147B (5243) │ │ │ │ +45BA7 Uncompressed Size 00007ACF (31439) │ │ │ │ +45BAB Filename Length 0018 (24) │ │ │ │ +45BAD Extra Length 001C (28) │ │ │ │ +45BAF Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x45BAF: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +45BC7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +45BC9 Length 0009 (9) │ │ │ │ +45BCB Flags 03 (3) 'Modification Access' │ │ │ │ +45BCC Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +45BD0 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +45BD4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +45BD6 Length 000B (11) │ │ │ │ +45BD8 Version 01 (1) │ │ │ │ +45BD9 UID Size 04 (4) │ │ │ │ +45BDA UID 00000000 (0) │ │ │ │ +45BDE GID Size 04 (4) │ │ │ │ +45BDF GID 00000000 (0) │ │ │ │ +45BE3 PAYLOAD │ │ │ │ + │ │ │ │ +4705E LOCAL HEADER #42 04034B50 (67324752) │ │ │ │ +47062 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +47063 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +47064 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +47066 Compression Method 0008 (8) 'Deflated' │ │ │ │ +47068 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +4706C CRC C14122F3 (3242271475) │ │ │ │ +47070 Compressed Size 000021E0 (8672) │ │ │ │ +47074 Uncompressed Size 0000D220 (53792) │ │ │ │ +47078 Filename Length 001F (31) │ │ │ │ +4707A Extra Length 001C (28) │ │ │ │ +4707C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4707C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +4709B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +4709D Length 0009 (9) │ │ │ │ +4709F Flags 03 (3) 'Modification Access' │ │ │ │ +470A0 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +470A4 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +470A8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +470AA Length 000B (11) │ │ │ │ +470AC Version 01 (1) │ │ │ │ +470AD UID Size 04 (4) │ │ │ │ +470AE UID 00000000 (0) │ │ │ │ +470B2 GID Size 04 (4) │ │ │ │ +470B3 GID 00000000 (0) │ │ │ │ +470B7 PAYLOAD │ │ │ │ + │ │ │ │ +49297 LOCAL HEADER #43 04034B50 (67324752) │ │ │ │ +4929B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4929C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4929D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4929F Compression Method 0008 (8) 'Deflated' │ │ │ │ +492A1 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +492A5 CRC E6EB8E0C (3874197004) │ │ │ │ +492A9 Compressed Size 000003F7 (1015) │ │ │ │ +492AD Uncompressed Size 000008A3 (2211) │ │ │ │ +492B1 Filename Length 001E (30) │ │ │ │ +492B3 Extra Length 001C (28) │ │ │ │ +492B5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x492B5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +492D3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +492D5 Length 0009 (9) │ │ │ │ +492D7 Flags 03 (3) 'Modification Access' │ │ │ │ +492D8 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +492DC Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +492E0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +492E2 Length 000B (11) │ │ │ │ +492E4 Version 01 (1) │ │ │ │ +492E5 UID Size 04 (4) │ │ │ │ +492E6 UID 00000000 (0) │ │ │ │ +492EA GID Size 04 (4) │ │ │ │ +492EB GID 00000000 (0) │ │ │ │ +492EF PAYLOAD │ │ │ │ + │ │ │ │ +496E6 LOCAL HEADER #44 04034B50 (67324752) │ │ │ │ +496EA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +496EB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +496EC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +496EE Compression Method 0008 (8) 'Deflated' │ │ │ │ +496F0 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +496F4 CRC 352C2657 (892085847) │ │ │ │ +496F8 Compressed Size 000042FE (17150) │ │ │ │ +496FC Uncompressed Size 0000DAE1 (56033) │ │ │ │ +49700 Filename Length 0013 (19) │ │ │ │ +49702 Extra Length 001C (28) │ │ │ │ +49704 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x49704: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +49717 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +49719 Length 0009 (9) │ │ │ │ +4971B Flags 03 (3) 'Modification Access' │ │ │ │ +4971C Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +49720 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +49724 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +49726 Length 000B (11) │ │ │ │ +49728 Version 01 (1) │ │ │ │ +49729 UID Size 04 (4) │ │ │ │ +4972A UID 00000000 (0) │ │ │ │ +4972E GID Size 04 (4) │ │ │ │ +4972F GID 00000000 (0) │ │ │ │ +49733 PAYLOAD │ │ │ │ + │ │ │ │ +4DA31 LOCAL HEADER #45 04034B50 (67324752) │ │ │ │ +4DA35 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4DA36 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4DA37 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4DA39 Compression Method 0008 (8) 'Deflated' │ │ │ │ +4DA3B Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +4DA3F CRC FCBDAAFD (4240288509) │ │ │ │ +4DA43 Compressed Size 000026C3 (9923) │ │ │ │ +4DA47 Uncompressed Size 00006E45 (28229) │ │ │ │ +4DA4B Filename Length 0019 (25) │ │ │ │ +4DA4D Extra Length 001C (28) │ │ │ │ +4DA4F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4DA4F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +4DA68 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +4DA6A Length 0009 (9) │ │ │ │ +4DA6C Flags 03 (3) 'Modification Access' │ │ │ │ +4DA6D Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +4DA71 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +4DA75 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +4DA77 Length 000B (11) │ │ │ │ +4DA79 Version 01 (1) │ │ │ │ +4DA7A UID Size 04 (4) │ │ │ │ +4DA7B UID 00000000 (0) │ │ │ │ +4DA7F GID Size 04 (4) │ │ │ │ +4DA80 GID 00000000 (0) │ │ │ │ +4DA84 PAYLOAD │ │ │ │ + │ │ │ │ +50147 LOCAL HEADER #46 04034B50 (67324752) │ │ │ │ +5014B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +5014C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +5014D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +5014F Compression Method 0008 (8) 'Deflated' │ │ │ │ +50151 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +50155 CRC BCB71496 (3166114966) │ │ │ │ +50159 Compressed Size 00002739 (10041) │ │ │ │ +5015D Uncompressed Size 00008B83 (35715) │ │ │ │ +50161 Filename Length 0019 (25) │ │ │ │ +50163 Extra Length 001C (28) │ │ │ │ +50165 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x50165: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +5017E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +50180 Length 0009 (9) │ │ │ │ +50182 Flags 03 (3) 'Modification Access' │ │ │ │ +50183 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +50187 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +5018B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +5018D Length 000B (11) │ │ │ │ +5018F Version 01 (1) │ │ │ │ +50190 UID Size 04 (4) │ │ │ │ +50191 UID 00000000 (0) │ │ │ │ +50195 GID Size 04 (4) │ │ │ │ +50196 GID 00000000 (0) │ │ │ │ +5019A PAYLOAD │ │ │ │ + │ │ │ │ +528D3 LOCAL HEADER #47 04034B50 (67324752) │ │ │ │ +528D7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +528D8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +528D9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +528DB Compression Method 0008 (8) 'Deflated' │ │ │ │ +528DD Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +528E1 CRC 69960DF9 (1771441657) │ │ │ │ +528E5 Compressed Size 00000ECD (3789) │ │ │ │ +528E9 Uncompressed Size 000053BF (21439) │ │ │ │ +528ED Filename Length 0021 (33) │ │ │ │ +528EF Extra Length 001C (28) │ │ │ │ +528F1 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x528F1: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +52912 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +52914 Length 0009 (9) │ │ │ │ +52916 Flags 03 (3) 'Modification Access' │ │ │ │ +52917 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +5291B Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +5291F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +52921 Length 000B (11) │ │ │ │ +52923 Version 01 (1) │ │ │ │ +52924 UID Size 04 (4) │ │ │ │ +52925 UID 00000000 (0) │ │ │ │ +52929 GID Size 04 (4) │ │ │ │ +5292A GID 00000000 (0) │ │ │ │ +5292E PAYLOAD │ │ │ │ + │ │ │ │ +537FB LOCAL HEADER #48 04034B50 (67324752) │ │ │ │ +537FF Extract Zip Spec 14 (20) '2.0' │ │ │ │ +53800 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +53801 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +53803 Compression Method 0008 (8) 'Deflated' │ │ │ │ +53805 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +53809 CRC 405AA2A7 (1079681703) │ │ │ │ +5380D Compressed Size 00000535 (1333) │ │ │ │ +53811 Uncompressed Size 00000C96 (3222) │ │ │ │ +53815 Filename Length 0017 (23) │ │ │ │ +53817 Extra Length 001C (28) │ │ │ │ +53819 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x53819: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +53830 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +53832 Length 0009 (9) │ │ │ │ +53834 Flags 03 (3) 'Modification Access' │ │ │ │ +53835 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +53839 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +5383D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +5383F Length 000B (11) │ │ │ │ +53841 Version 01 (1) │ │ │ │ +53842 UID Size 04 (4) │ │ │ │ +53843 UID 00000000 (0) │ │ │ │ +53847 GID Size 04 (4) │ │ │ │ +53848 GID 00000000 (0) │ │ │ │ +5384C PAYLOAD │ │ │ │ + │ │ │ │ +53D81 LOCAL HEADER #49 04034B50 (67324752) │ │ │ │ +53D85 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +53D86 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +53D87 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +53D89 Compression Method 0008 (8) 'Deflated' │ │ │ │ +53D8B Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +53D8F CRC DB679B08 (3681000200) │ │ │ │ +53D93 Compressed Size 00000467 (1127) │ │ │ │ +53D97 Uncompressed Size 00000931 (2353) │ │ │ │ +53D9B Filename Length 001B (27) │ │ │ │ +53D9D Extra Length 001C (28) │ │ │ │ +53D9F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x53D9F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +53DBA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +53DBC Length 0009 (9) │ │ │ │ +53DBE Flags 03 (3) 'Modification Access' │ │ │ │ +53DBF Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +53DC3 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +53DC7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +53DC9 Length 000B (11) │ │ │ │ +53DCB Version 01 (1) │ │ │ │ +53DCC UID Size 04 (4) │ │ │ │ +53DCD UID 00000000 (0) │ │ │ │ +53DD1 GID Size 04 (4) │ │ │ │ +53DD2 GID 00000000 (0) │ │ │ │ +53DD6 PAYLOAD │ │ │ │ + │ │ │ │ +5423D LOCAL HEADER #50 04034B50 (67324752) │ │ │ │ +54241 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +54242 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +54243 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +54245 Compression Method 0008 (8) 'Deflated' │ │ │ │ +54247 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +5424B CRC 151A8F75 (354062197) │ │ │ │ +5424F Compressed Size 000016F1 (5873) │ │ │ │ +54253 Uncompressed Size 00007A6D (31341) │ │ │ │ +54257 Filename Length 001F (31) │ │ │ │ +54259 Extra Length 001C (28) │ │ │ │ +5425B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x5425B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +5427A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +5427C Length 0009 (9) │ │ │ │ +5427E Flags 03 (3) 'Modification Access' │ │ │ │ +5427F Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +54283 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +54287 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +54289 Length 000B (11) │ │ │ │ +5428B Version 01 (1) │ │ │ │ +5428C UID Size 04 (4) │ │ │ │ +5428D UID 00000000 (0) │ │ │ │ +54291 GID Size 04 (4) │ │ │ │ +54292 GID 00000000 (0) │ │ │ │ +54296 PAYLOAD │ │ │ │ + │ │ │ │ +55987 LOCAL HEADER #51 04034B50 (67324752) │ │ │ │ +5598B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +5598C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +5598D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +5598F Compression Method 0008 (8) 'Deflated' │ │ │ │ +55991 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +55995 CRC A615B373 (2786440051) │ │ │ │ +55999 Compressed Size 00004162 (16738) │ │ │ │ +5599D Uncompressed Size 0001D15F (119135) │ │ │ │ +559A1 Filename Length 0010 (16) │ │ │ │ +559A3 Extra Length 001C (28) │ │ │ │ +559A5 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x559A5: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +559B5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +559B7 Length 0009 (9) │ │ │ │ +559B9 Flags 03 (3) 'Modification Access' │ │ │ │ +559BA Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +559BE Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +559C2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +559C4 Length 000B (11) │ │ │ │ +559C6 Version 01 (1) │ │ │ │ +559C7 UID Size 04 (4) │ │ │ │ +559C8 UID 00000000 (0) │ │ │ │ +559CC GID Size 04 (4) │ │ │ │ +559CD GID 00000000 (0) │ │ │ │ +559D1 PAYLOAD │ │ │ │ + │ │ │ │ +59B33 LOCAL HEADER #52 04034B50 (67324752) │ │ │ │ +59B37 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +59B38 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +59B39 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +59B3B Compression Method 0008 (8) 'Deflated' │ │ │ │ +59B3D Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +59B41 CRC E65F34EA (3864999146) │ │ │ │ +59B45 Compressed Size 00000AE6 (2790) │ │ │ │ +59B49 Uncompressed Size 00002229 (8745) │ │ │ │ +59B4D Filename Length 0014 (20) │ │ │ │ +59B4F Extra Length 001C (28) │ │ │ │ +59B51 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x59B51: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +59B65 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +59B67 Length 0009 (9) │ │ │ │ +59B69 Flags 03 (3) 'Modification Access' │ │ │ │ +59B6A Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +59B6E Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +59B72 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +59B74 Length 000B (11) │ │ │ │ +59B76 Version 01 (1) │ │ │ │ +59B77 UID Size 04 (4) │ │ │ │ +59B78 UID 00000000 (0) │ │ │ │ +59B7C GID Size 04 (4) │ │ │ │ +59B7D GID 00000000 (0) │ │ │ │ +59B81 PAYLOAD │ │ │ │ + │ │ │ │ +5A667 LOCAL HEADER #53 04034B50 (67324752) │ │ │ │ +5A66B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +5A66C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +5A66D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +5A66F Compression Method 0008 (8) 'Deflated' │ │ │ │ +5A671 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +5A675 CRC CC7897DB (3430455259) │ │ │ │ +5A679 Compressed Size 0000B53D (46397) │ │ │ │ +5A67D Uncompressed Size 000418FB (268539) │ │ │ │ +5A681 Filename Length 0017 (23) │ │ │ │ +5A683 Extra Length 001C (28) │ │ │ │ +5A685 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x5A685: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +5A69C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +5A69E Length 0009 (9) │ │ │ │ +5A6A0 Flags 03 (3) 'Modification Access' │ │ │ │ +5A6A1 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +5A6A5 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +5A6A9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +5A6AB Length 000B (11) │ │ │ │ +5A6AD Version 01 (1) │ │ │ │ +5A6AE UID Size 04 (4) │ │ │ │ +5A6AF UID 00000000 (0) │ │ │ │ +5A6B3 GID Size 04 (4) │ │ │ │ +5A6B4 GID 00000000 (0) │ │ │ │ +5A6B8 PAYLOAD │ │ │ │ + │ │ │ │ +65BF5 LOCAL HEADER #54 04034B50 (67324752) │ │ │ │ +65BF9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +65BFA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +65BFB General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +65BFD Compression Method 0008 (8) 'Deflated' │ │ │ │ +65BFF Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +65C03 CRC CEBA6EB0 (3468324528) │ │ │ │ +65C07 Compressed Size 000003FF (1023) │ │ │ │ +65C0B Uncompressed Size 0000093D (2365) │ │ │ │ +65C0F Filename Length 0013 (19) │ │ │ │ +65C11 Extra Length 001C (28) │ │ │ │ +65C13 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x65C13: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +65C26 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +65C28 Length 0009 (9) │ │ │ │ +65C2A Flags 03 (3) 'Modification Access' │ │ │ │ +65C2B Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +65C2F Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +65C33 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +65C35 Length 000B (11) │ │ │ │ +65C37 Version 01 (1) │ │ │ │ +65C38 UID Size 04 (4) │ │ │ │ +65C39 UID 00000000 (0) │ │ │ │ +65C3D GID Size 04 (4) │ │ │ │ +65C3E GID 00000000 (0) │ │ │ │ +65C42 PAYLOAD │ │ │ │ + │ │ │ │ +66041 LOCAL HEADER #55 04034B50 (67324752) │ │ │ │ +66045 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +66046 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +66047 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +66049 Compression Method 0008 (8) 'Deflated' │ │ │ │ +6604B Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +6604F CRC FD36DFE1 (4248231905) │ │ │ │ +66053 Compressed Size 000014D7 (5335) │ │ │ │ +66057 Uncompressed Size 00006892 (26770) │ │ │ │ +6605B Filename Length 0012 (18) │ │ │ │ +6605D Extra Length 001C (28) │ │ │ │ +6605F Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6605F: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +66071 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +66073 Length 0009 (9) │ │ │ │ +66075 Flags 03 (3) 'Modification Access' │ │ │ │ +66076 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +6607A Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +6607E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +66080 Length 000B (11) │ │ │ │ +66082 Version 01 (1) │ │ │ │ +66083 UID Size 04 (4) │ │ │ │ +66084 UID 00000000 (0) │ │ │ │ +66088 GID Size 04 (4) │ │ │ │ +66089 GID 00000000 (0) │ │ │ │ +6608D PAYLOAD │ │ │ │ + │ │ │ │ +67564 LOCAL HEADER #56 04034B50 (67324752) │ │ │ │ +67568 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +67569 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6756A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6756C Compression Method 0008 (8) 'Deflated' │ │ │ │ +6756E Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +67572 CRC EC2D0A1B (3962374683) │ │ │ │ +67576 Compressed Size 000011EB (4587) │ │ │ │ +6757A Uncompressed Size 000040FA (16634) │ │ │ │ +6757E Filename Length 0012 (18) │ │ │ │ +67580 Extra Length 001C (28) │ │ │ │ +67582 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x67582: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +67594 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +67596 Length 0009 (9) │ │ │ │ +67598 Flags 03 (3) 'Modification Access' │ │ │ │ +67599 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +6759D Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +675A1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +675A3 Length 000B (11) │ │ │ │ +675A5 Version 01 (1) │ │ │ │ +675A6 UID Size 04 (4) │ │ │ │ +675A7 UID 00000000 (0) │ │ │ │ +675AB GID Size 04 (4) │ │ │ │ +675AC GID 00000000 (0) │ │ │ │ +675B0 PAYLOAD │ │ │ │ + │ │ │ │ +6879B LOCAL HEADER #57 04034B50 (67324752) │ │ │ │ +6879F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +687A0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +687A1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +687A3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +687A5 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +687A9 CRC 34ABD5D4 (883676628) │ │ │ │ +687AD Compressed Size 000009D8 (2520) │ │ │ │ +687B1 Uncompressed Size 00003529 (13609) │ │ │ │ +687B5 Filename Length 0019 (25) │ │ │ │ +687B7 Extra Length 001C (28) │ │ │ │ +687B9 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x687B9: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +687D2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +687D4 Length 0009 (9) │ │ │ │ +687D6 Flags 03 (3) 'Modification Access' │ │ │ │ +687D7 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +687DB Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +687DF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +687E1 Length 000B (11) │ │ │ │ +687E3 Version 01 (1) │ │ │ │ +687E4 UID Size 04 (4) │ │ │ │ +687E5 UID 00000000 (0) │ │ │ │ +687E9 GID Size 04 (4) │ │ │ │ +687EA GID 00000000 (0) │ │ │ │ +687EE PAYLOAD │ │ │ │ + │ │ │ │ +691C6 LOCAL HEADER #58 04034B50 (67324752) │ │ │ │ +691CA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +691CB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +691CC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +691CE Compression Method 0008 (8) 'Deflated' │ │ │ │ +691D0 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +691D4 CRC 3CD95A4D (1020877389) │ │ │ │ +691D8 Compressed Size 000018B5 (6325) │ │ │ │ +691DC Uncompressed Size 0000A678 (42616) │ │ │ │ +691E0 Filename Length 0019 (25) │ │ │ │ +691E2 Extra Length 001C (28) │ │ │ │ +691E4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x691E4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +691FD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +691FF Length 0009 (9) │ │ │ │ +69201 Flags 03 (3) 'Modification Access' │ │ │ │ +69202 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +69206 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +6920A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6920C Length 000B (11) │ │ │ │ +6920E Version 01 (1) │ │ │ │ +6920F UID Size 04 (4) │ │ │ │ +69210 UID 00000000 (0) │ │ │ │ +69214 GID Size 04 (4) │ │ │ │ +69215 GID 00000000 (0) │ │ │ │ +69219 PAYLOAD │ │ │ │ + │ │ │ │ +6AACE LOCAL HEADER #59 04034B50 (67324752) │ │ │ │ +6AAD2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6AAD3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6AAD4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6AAD6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +6AAD8 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +6AADC CRC EB9538F3 (3952425203) │ │ │ │ +6AAE0 Compressed Size 0000177C (6012) │ │ │ │ +6AAE4 Uncompressed Size 0000472C (18220) │ │ │ │ +6AAE8 Filename Length 0014 (20) │ │ │ │ +6AAEA Extra Length 001C (28) │ │ │ │ +6AAEC Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6AAEC: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6AB00 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6AB02 Length 0009 (9) │ │ │ │ +6AB04 Flags 03 (3) 'Modification Access' │ │ │ │ +6AB05 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +6AB09 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +6AB0D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6AB0F Length 000B (11) │ │ │ │ +6AB11 Version 01 (1) │ │ │ │ +6AB12 UID Size 04 (4) │ │ │ │ +6AB13 UID 00000000 (0) │ │ │ │ +6AB17 GID Size 04 (4) │ │ │ │ +6AB18 GID 00000000 (0) │ │ │ │ +6AB1C PAYLOAD │ │ │ │ + │ │ │ │ +6C298 LOCAL HEADER #60 04034B50 (67324752) │ │ │ │ +6C29C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6C29D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6C29E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6C2A0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +6C2A2 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +6C2A6 CRC F6F034FB (4142937339) │ │ │ │ +6C2AA Compressed Size 00000409 (1033) │ │ │ │ +6C2AE Uncompressed Size 00000825 (2085) │ │ │ │ +6C2B2 Filename Length 001C (28) │ │ │ │ +6C2B4 Extra Length 001C (28) │ │ │ │ +6C2B6 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6C2B6: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6C2D2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6C2D4 Length 0009 (9) │ │ │ │ +6C2D6 Flags 03 (3) 'Modification Access' │ │ │ │ +6C2D7 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +6C2DB Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +6C2DF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6C2E1 Length 000B (11) │ │ │ │ +6C2E3 Version 01 (1) │ │ │ │ +6C2E4 UID Size 04 (4) │ │ │ │ +6C2E5 UID 00000000 (0) │ │ │ │ +6C2E9 GID Size 04 (4) │ │ │ │ +6C2EA GID 00000000 (0) │ │ │ │ +6C2EE PAYLOAD │ │ │ │ + │ │ │ │ +6C6F7 LOCAL HEADER #61 04034B50 (67324752) │ │ │ │ +6C6FB Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6C6FC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6C6FD General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6C6FF Compression Method 0008 (8) 'Deflated' │ │ │ │ +6C701 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +6C705 CRC D734912E (3610546478) │ │ │ │ +6C709 Compressed Size 000024BD (9405) │ │ │ │ +6C70D Uncompressed Size 0000B65B (46683) │ │ │ │ +6C711 Filename Length 001F (31) │ │ │ │ +6C713 Extra Length 001C (28) │ │ │ │ +6C715 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6C715: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6C734 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6C736 Length 0009 (9) │ │ │ │ +6C738 Flags 03 (3) 'Modification Access' │ │ │ │ +6C739 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +6C73D Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +6C741 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6C743 Length 000B (11) │ │ │ │ +6C745 Version 01 (1) │ │ │ │ +6C746 UID Size 04 (4) │ │ │ │ +6C747 UID 00000000 (0) │ │ │ │ +6C74B GID Size 04 (4) │ │ │ │ +6C74C GID 00000000 (0) │ │ │ │ +6C750 PAYLOAD │ │ │ │ + │ │ │ │ +6EC0D LOCAL HEADER #62 04034B50 (67324752) │ │ │ │ +6EC11 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6EC12 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6EC13 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6EC15 Compression Method 0008 (8) 'Deflated' │ │ │ │ +6EC17 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +6EC1B CRC 9DF820AC (2650284204) │ │ │ │ +6EC1F Compressed Size 00000E7E (3710) │ │ │ │ +6EC23 Uncompressed Size 000052D9 (21209) │ │ │ │ +6EC27 Filename Length 001F (31) │ │ │ │ +6EC29 Extra Length 001C (28) │ │ │ │ +6EC2B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6EC2B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6EC4A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6EC4C Length 0009 (9) │ │ │ │ +6EC4E Flags 03 (3) 'Modification Access' │ │ │ │ +6EC4F Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +6EC53 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +6EC57 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6EC59 Length 000B (11) │ │ │ │ +6EC5B Version 01 (1) │ │ │ │ +6EC5C UID Size 04 (4) │ │ │ │ +6EC5D UID 00000000 (0) │ │ │ │ +6EC61 GID Size 04 (4) │ │ │ │ +6EC62 GID 00000000 (0) │ │ │ │ +6EC66 PAYLOAD │ │ │ │ + │ │ │ │ +6FAE4 LOCAL HEADER #63 04034B50 (67324752) │ │ │ │ +6FAE8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6FAE9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6FAEA General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6FAEC Compression Method 0008 (8) 'Deflated' │ │ │ │ +6FAEE Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +6FAF2 CRC FEC753B6 (4274475958) │ │ │ │ +6FAF6 Compressed Size 00000A45 (2629) │ │ │ │ +6FAFA Uncompressed Size 0000247A (9338) │ │ │ │ +6FAFE Filename Length 0013 (19) │ │ │ │ +6FB00 Extra Length 001C (28) │ │ │ │ +6FB02 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6FB02: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6FB15 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6FB17 Length 0009 (9) │ │ │ │ +6FB19 Flags 03 (3) 'Modification Access' │ │ │ │ +6FB1A Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +6FB1E Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +6FB22 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6FB24 Length 000B (11) │ │ │ │ +6FB26 Version 01 (1) │ │ │ │ +6FB27 UID Size 04 (4) │ │ │ │ +6FB28 UID 00000000 (0) │ │ │ │ +6FB2C GID Size 04 (4) │ │ │ │ +6FB2D GID 00000000 (0) │ │ │ │ +6FB31 PAYLOAD │ │ │ │ + │ │ │ │ +70576 LOCAL HEADER #64 04034B50 (67324752) │ │ │ │ +7057A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +7057B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +7057C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +7057E Compression Method 0008 (8) 'Deflated' │ │ │ │ +70580 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +70584 CRC 29736947 (695429447) │ │ │ │ +70588 Compressed Size 0000257D (9597) │ │ │ │ +7058C Uncompressed Size 0000BA5F (47711) │ │ │ │ +70590 Filename Length 0019 (25) │ │ │ │ +70592 Extra Length 001C (28) │ │ │ │ +70594 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x70594: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +705AD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +705AF Length 0009 (9) │ │ │ │ +705B1 Flags 03 (3) 'Modification Access' │ │ │ │ +705B2 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +705B6 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +705BA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +705BC Length 000B (11) │ │ │ │ +705BE Version 01 (1) │ │ │ │ +705BF UID Size 04 (4) │ │ │ │ +705C0 UID 00000000 (0) │ │ │ │ +705C4 GID Size 04 (4) │ │ │ │ +705C5 GID 00000000 (0) │ │ │ │ +705C9 PAYLOAD │ │ │ │ + │ │ │ │ +72B46 LOCAL HEADER #65 04034B50 (67324752) │ │ │ │ +72B4A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +72B4B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +72B4C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +72B4E Compression Method 0008 (8) 'Deflated' │ │ │ │ +72B50 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +72B54 CRC BF3A6C6B (3208277099) │ │ │ │ +72B58 Compressed Size 00000EFB (3835) │ │ │ │ +72B5C Uncompressed Size 00003A2C (14892) │ │ │ │ +72B60 Filename Length 0024 (36) │ │ │ │ +72B62 Extra Length 001C (28) │ │ │ │ +72B64 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x72B64: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +72B88 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +72B8A Length 0009 (9) │ │ │ │ +72B8C Flags 03 (3) 'Modification Access' │ │ │ │ +72B8D Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +72B91 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +72B95 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +72B97 Length 000B (11) │ │ │ │ +72B99 Version 01 (1) │ │ │ │ +72B9A UID Size 04 (4) │ │ │ │ +72B9B UID 00000000 (0) │ │ │ │ +72B9F GID Size 04 (4) │ │ │ │ +72BA0 GID 00000000 (0) │ │ │ │ +72BA4 PAYLOAD │ │ │ │ + │ │ │ │ +73A9F LOCAL HEADER #66 04034B50 (67324752) │ │ │ │ +73AA3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +73AA4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +73AA5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +73AA7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +73AA9 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +73AAD CRC CB6AD0BA (3412775098) │ │ │ │ +73AB1 Compressed Size 00001AEC (6892) │ │ │ │ +73AB5 Uncompressed Size 00005F82 (24450) │ │ │ │ +73AB9 Filename Length 0017 (23) │ │ │ │ +73ABB Extra Length 001C (28) │ │ │ │ +73ABD Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x73ABD: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +73AD4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +73AD6 Length 0009 (9) │ │ │ │ +73AD8 Flags 03 (3) 'Modification Access' │ │ │ │ +73AD9 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +73ADD Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +73AE1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +73AE3 Length 000B (11) │ │ │ │ +73AE5 Version 01 (1) │ │ │ │ +73AE6 UID Size 04 (4) │ │ │ │ +73AE7 UID 00000000 (0) │ │ │ │ +73AEB GID Size 04 (4) │ │ │ │ +73AEC GID 00000000 (0) │ │ │ │ +73AF0 PAYLOAD │ │ │ │ + │ │ │ │ +755DC LOCAL HEADER #67 04034B50 (67324752) │ │ │ │ +755E0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +755E1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +755E2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +755E4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +755E6 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +755EA CRC 11E32AF1 (300100337) │ │ │ │ +755EE Compressed Size 00000ED3 (3795) │ │ │ │ +755F2 Uncompressed Size 000038E2 (14562) │ │ │ │ +755F6 Filename Length 0023 (35) │ │ │ │ +755F8 Extra Length 001C (28) │ │ │ │ +755FA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x755FA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +7561D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +7561F Length 0009 (9) │ │ │ │ +75621 Flags 03 (3) 'Modification Access' │ │ │ │ +75622 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +75626 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +7562A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +7562C Length 000B (11) │ │ │ │ +7562E Version 01 (1) │ │ │ │ +7562F UID Size 04 (4) │ │ │ │ +75630 UID 00000000 (0) │ │ │ │ +75634 GID Size 04 (4) │ │ │ │ +75635 GID 00000000 (0) │ │ │ │ +75639 PAYLOAD │ │ │ │ + │ │ │ │ +7650C LOCAL HEADER #68 04034B50 (67324752) │ │ │ │ +76510 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +76511 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +76512 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +76514 Compression Method 0008 (8) 'Deflated' │ │ │ │ +76516 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +7651A CRC 2DB7929F (767005343) │ │ │ │ +7651E Compressed Size 00000113 (275) │ │ │ │ +76522 Uncompressed Size 000001F3 (499) │ │ │ │ +76526 Filename Length 001B (27) │ │ │ │ +76528 Extra Length 001C (28) │ │ │ │ +7652A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x7652A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +76545 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +76547 Length 0009 (9) │ │ │ │ +76549 Flags 03 (3) 'Modification Access' │ │ │ │ +7654A Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +7654E Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +76552 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +76554 Length 000B (11) │ │ │ │ +76556 Version 01 (1) │ │ │ │ +76557 UID Size 04 (4) │ │ │ │ +76558 UID 00000000 (0) │ │ │ │ +7655C GID Size 04 (4) │ │ │ │ +7655D GID 00000000 (0) │ │ │ │ +76561 PAYLOAD │ │ │ │ + │ │ │ │ +76674 LOCAL HEADER #69 04034B50 (67324752) │ │ │ │ +76678 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +76679 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +7667A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +7667C Compression Method 0008 (8) 'Deflated' │ │ │ │ +7667E Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +76682 CRC E6350A39 (3862235705) │ │ │ │ +76686 Compressed Size 0000188E (6286) │ │ │ │ +7668A Uncompressed Size 00008FAC (36780) │ │ │ │ +7668E Filename Length 001D (29) │ │ │ │ +76690 Extra Length 001C (28) │ │ │ │ +76692 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x76692: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +766AF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +766B1 Length 0009 (9) │ │ │ │ +766B3 Flags 03 (3) 'Modification Access' │ │ │ │ +766B4 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +766B8 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +766BC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +766BE Length 000B (11) │ │ │ │ +766C0 Version 01 (1) │ │ │ │ +766C1 UID Size 04 (4) │ │ │ │ +766C2 UID 00000000 (0) │ │ │ │ +766C6 GID Size 04 (4) │ │ │ │ +766C7 GID 00000000 (0) │ │ │ │ +766CB PAYLOAD │ │ │ │ + │ │ │ │ +77F59 LOCAL HEADER #70 04034B50 (67324752) │ │ │ │ +77F5D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +77F5E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +77F5F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +77F61 Compression Method 0008 (8) 'Deflated' │ │ │ │ +77F63 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +77F67 CRC 905D47D7 (2422032343) │ │ │ │ +77F6B Compressed Size 0000164C (5708) │ │ │ │ +77F6F Uncompressed Size 00003A9B (15003) │ │ │ │ +77F73 Filename Length 0015 (21) │ │ │ │ +77F75 Extra Length 001C (28) │ │ │ │ +77F77 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x77F77: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +77F8C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +77F8E Length 0009 (9) │ │ │ │ +77F90 Flags 03 (3) 'Modification Access' │ │ │ │ +77F91 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +77F95 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +77F99 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +77F9B Length 000B (11) │ │ │ │ +77F9D Version 01 (1) │ │ │ │ +77F9E UID Size 04 (4) │ │ │ │ +77F9F UID 00000000 (0) │ │ │ │ +77FA3 GID Size 04 (4) │ │ │ │ +77FA4 GID 00000000 (0) │ │ │ │ +77FA8 PAYLOAD │ │ │ │ + │ │ │ │ +795F4 LOCAL HEADER #71 04034B50 (67324752) │ │ │ │ +795F8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +795F9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +795FA General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +795FC Compression Method 0008 (8) 'Deflated' │ │ │ │ +795FE Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +79602 CRC 60E34873 (1625507955) │ │ │ │ +79606 Compressed Size 000040BC (16572) │ │ │ │ +7960A Uncompressed Size 00013397 (78743) │ │ │ │ +7960E Filename Length 0016 (22) │ │ │ │ +79610 Extra Length 001C (28) │ │ │ │ +79612 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x79612: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +79628 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +7962A Length 0009 (9) │ │ │ │ +7962C Flags 03 (3) 'Modification Access' │ │ │ │ +7962D Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +79631 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +79635 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +79637 Length 000B (11) │ │ │ │ +79639 Version 01 (1) │ │ │ │ +7963A UID Size 04 (4) │ │ │ │ +7963B UID 00000000 (0) │ │ │ │ +7963F GID Size 04 (4) │ │ │ │ +79640 GID 00000000 (0) │ │ │ │ +79644 PAYLOAD │ │ │ │ + │ │ │ │ +7D700 LOCAL HEADER #72 04034B50 (67324752) │ │ │ │ +7D704 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +7D705 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +7D706 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +7D708 Compression Method 0008 (8) 'Deflated' │ │ │ │ +7D70A Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +7D70E CRC 53039E88 (1392746120) │ │ │ │ +7D712 Compressed Size 00003E86 (16006) │ │ │ │ +7D716 Uncompressed Size 0001C17B (115067) │ │ │ │ +7D71A Filename Length 0019 (25) │ │ │ │ +7D71C Extra Length 001C (28) │ │ │ │ +7D71E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x7D71E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +7D737 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +7D739 Length 0009 (9) │ │ │ │ +7D73B Flags 03 (3) 'Modification Access' │ │ │ │ +7D73C Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +7D740 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +7D744 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +7D746 Length 000B (11) │ │ │ │ +7D748 Version 01 (1) │ │ │ │ +7D749 UID Size 04 (4) │ │ │ │ +7D74A UID 00000000 (0) │ │ │ │ +7D74E GID Size 04 (4) │ │ │ │ +7D74F GID 00000000 (0) │ │ │ │ +7D753 PAYLOAD │ │ │ │ + │ │ │ │ +815D9 LOCAL HEADER #73 04034B50 (67324752) │ │ │ │ +815DD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +815DE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +815DF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +815E1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +815E3 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +815E7 CRC 7E351E59 (2117410393) │ │ │ │ +815EB Compressed Size 00000892 (2194) │ │ │ │ +815EF Uncompressed Size 0000362E (13870) │ │ │ │ +815F3 Filename Length 0011 (17) │ │ │ │ +815F5 Extra Length 001C (28) │ │ │ │ +815F7 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x815F7: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +81608 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +8160A Length 0009 (9) │ │ │ │ +8160C Flags 03 (3) 'Modification Access' │ │ │ │ +8160D Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +81611 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +81615 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +81617 Length 000B (11) │ │ │ │ +81619 Version 01 (1) │ │ │ │ +8161A UID Size 04 (4) │ │ │ │ +8161B UID 00000000 (0) │ │ │ │ +8161F GID Size 04 (4) │ │ │ │ +81620 GID 00000000 (0) │ │ │ │ +81624 PAYLOAD │ │ │ │ + │ │ │ │ +81EB6 LOCAL HEADER #74 04034B50 (67324752) │ │ │ │ +81EBA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +81EBB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +81EBC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +81EBE Compression Method 0008 (8) 'Deflated' │ │ │ │ +81EC0 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +81EC4 CRC DE1DF177 (3726504311) │ │ │ │ +81EC8 Compressed Size 000051B8 (20920) │ │ │ │ +81ECC Uncompressed Size 0001FBF7 (130039) │ │ │ │ +81ED0 Filename Length 0015 (21) │ │ │ │ +81ED2 Extra Length 001C (28) │ │ │ │ +81ED4 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x81ED4: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +81EE9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +81EEB Length 0009 (9) │ │ │ │ +81EED Flags 03 (3) 'Modification Access' │ │ │ │ +81EEE Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +81EF2 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +81EF6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +81EF8 Length 000B (11) │ │ │ │ +81EFA Version 01 (1) │ │ │ │ +81EFB UID Size 04 (4) │ │ │ │ +81EFC UID 00000000 (0) │ │ │ │ +81F00 GID Size 04 (4) │ │ │ │ +81F01 GID 00000000 (0) │ │ │ │ +81F05 PAYLOAD │ │ │ │ + │ │ │ │ +870BD LOCAL HEADER #75 04034B50 (67324752) │ │ │ │ +870C1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +870C2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +870C3 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +870C5 Compression Method 0008 (8) 'Deflated' │ │ │ │ +870C7 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +870CB CRC 587B6125 (1484480805) │ │ │ │ +870CF Compressed Size 00001C42 (7234) │ │ │ │ +870D3 Uncompressed Size 00008AC2 (35522) │ │ │ │ +870D7 Filename Length 0019 (25) │ │ │ │ +870D9 Extra Length 001C (28) │ │ │ │ +870DB Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x870DB: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +870F4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +870F6 Length 0009 (9) │ │ │ │ +870F8 Flags 03 (3) 'Modification Access' │ │ │ │ +870F9 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +870FD Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +87101 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +87103 Length 000B (11) │ │ │ │ +87105 Version 01 (1) │ │ │ │ +87106 UID Size 04 (4) │ │ │ │ +87107 UID 00000000 (0) │ │ │ │ +8710B GID Size 04 (4) │ │ │ │ +8710C GID 00000000 (0) │ │ │ │ +87110 PAYLOAD │ │ │ │ + │ │ │ │ +88D52 LOCAL HEADER #76 04034B50 (67324752) │ │ │ │ +88D56 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +88D57 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +88D58 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +88D5A Compression Method 0008 (8) 'Deflated' │ │ │ │ +88D5C Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +88D60 CRC 0EC11074 (247533684) │ │ │ │ +88D64 Compressed Size 00000D95 (3477) │ │ │ │ +88D68 Uncompressed Size 00002E9F (11935) │ │ │ │ +88D6C Filename Length 0018 (24) │ │ │ │ +88D6E Extra Length 001C (28) │ │ │ │ +88D70 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x88D70: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +88D88 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +88D8A Length 0009 (9) │ │ │ │ +88D8C Flags 03 (3) 'Modification Access' │ │ │ │ +88D8D Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +88D91 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +88D95 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +88D97 Length 000B (11) │ │ │ │ +88D99 Version 01 (1) │ │ │ │ +88D9A UID Size 04 (4) │ │ │ │ +88D9B UID 00000000 (0) │ │ │ │ +88D9F GID Size 04 (4) │ │ │ │ +88DA0 GID 00000000 (0) │ │ │ │ +88DA4 PAYLOAD │ │ │ │ + │ │ │ │ +89B39 LOCAL HEADER #77 04034B50 (67324752) │ │ │ │ +89B3D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +89B3E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +89B3F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +89B41 Compression Method 0008 (8) 'Deflated' │ │ │ │ +89B43 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +89B47 CRC E5DE5170 (3856552304) │ │ │ │ +89B4B Compressed Size 000001DF (479) │ │ │ │ +89B4F Uncompressed Size 00000323 (803) │ │ │ │ +89B53 Filename Length 0011 (17) │ │ │ │ +89B55 Extra Length 001C (28) │ │ │ │ +89B57 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x89B57: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +89B68 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +89B6A Length 0009 (9) │ │ │ │ +89B6C Flags 03 (3) 'Modification Access' │ │ │ │ +89B6D Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +89B71 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +89B75 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +89B77 Length 000B (11) │ │ │ │ +89B79 Version 01 (1) │ │ │ │ +89B7A UID Size 04 (4) │ │ │ │ +89B7B UID 00000000 (0) │ │ │ │ +89B7F GID Size 04 (4) │ │ │ │ +89B80 GID 00000000 (0) │ │ │ │ +89B84 PAYLOAD │ │ │ │ + │ │ │ │ +89D63 LOCAL HEADER #78 04034B50 (67324752) │ │ │ │ +89D67 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +89D68 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +89D69 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +89D6B Compression Method 0008 (8) 'Deflated' │ │ │ │ +89D6D Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +89D71 CRC AA4987E4 (2856945636) │ │ │ │ +89D75 Compressed Size 000006BD (1725) │ │ │ │ +89D79 Uncompressed Size 0000141E (5150) │ │ │ │ +89D7D Filename Length 0019 (25) │ │ │ │ +89D7F Extra Length 001C (28) │ │ │ │ +89D81 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x89D81: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +89D9A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +89D9C Length 0009 (9) │ │ │ │ +89D9E Flags 03 (3) 'Modification Access' │ │ │ │ +89D9F Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +89DA3 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +89DA7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +89DA9 Length 000B (11) │ │ │ │ +89DAB Version 01 (1) │ │ │ │ +89DAC UID Size 04 (4) │ │ │ │ +89DAD UID 00000000 (0) │ │ │ │ +89DB1 GID Size 04 (4) │ │ │ │ +89DB2 GID 00000000 (0) │ │ │ │ +89DB6 PAYLOAD │ │ │ │ + │ │ │ │ +8A473 LOCAL HEADER #79 04034B50 (67324752) │ │ │ │ +8A477 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +8A478 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8A479 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8A47B Compression Method 0008 (8) 'Deflated' │ │ │ │ +8A47D Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +8A481 CRC 594C717E (1498182014) │ │ │ │ +8A485 Compressed Size 00001B89 (7049) │ │ │ │ +8A489 Uncompressed Size 00009F5F (40799) │ │ │ │ +8A48D Filename Length 0018 (24) │ │ │ │ +8A48F Extra Length 001C (28) │ │ │ │ +8A491 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8A491: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +8A4A9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +8A4AB Length 0009 (9) │ │ │ │ +8A4AD Flags 03 (3) 'Modification Access' │ │ │ │ +8A4AE Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +8A4B2 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +8A4B6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8A4B8 Length 000B (11) │ │ │ │ +8A4BA Version 01 (1) │ │ │ │ +8A4BB UID Size 04 (4) │ │ │ │ +8A4BC UID 00000000 (0) │ │ │ │ +8A4C0 GID Size 04 (4) │ │ │ │ +8A4C1 GID 00000000 (0) │ │ │ │ +8A4C5 PAYLOAD │ │ │ │ + │ │ │ │ +8C04E LOCAL HEADER #80 04034B50 (67324752) │ │ │ │ +8C052 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +8C053 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8C054 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8C056 Compression Method 0008 (8) 'Deflated' │ │ │ │ +8C058 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +8C05C CRC 0132E09D (20111517) │ │ │ │ +8C060 Compressed Size 000016FE (5886) │ │ │ │ +8C064 Uncompressed Size 00008B12 (35602) │ │ │ │ +8C068 Filename Length 0012 (18) │ │ │ │ +8C06A Extra Length 001C (28) │ │ │ │ +8C06C Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8C06C: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +8C07E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +8C080 Length 0009 (9) │ │ │ │ +8C082 Flags 03 (3) 'Modification Access' │ │ │ │ +8C083 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +8C087 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +8C08B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8C08D Length 000B (11) │ │ │ │ +8C08F Version 01 (1) │ │ │ │ +8C090 UID Size 04 (4) │ │ │ │ +8C091 UID 00000000 (0) │ │ │ │ +8C095 GID Size 04 (4) │ │ │ │ +8C096 GID 00000000 (0) │ │ │ │ +8C09A PAYLOAD │ │ │ │ + │ │ │ │ +8D798 LOCAL HEADER #81 04034B50 (67324752) │ │ │ │ +8D79C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +8D79D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8D79E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8D7A0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +8D7A2 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +8D7A6 CRC 8CB6D480 (2360792192) │ │ │ │ +8D7AA Compressed Size 00001E10 (7696) │ │ │ │ +8D7AE Uncompressed Size 00008803 (34819) │ │ │ │ +8D7B2 Filename Length 0016 (22) │ │ │ │ +8D7B4 Extra Length 001C (28) │ │ │ │ +8D7B6 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8D7B6: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +8D7CC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +8D7CE Length 0009 (9) │ │ │ │ +8D7D0 Flags 03 (3) 'Modification Access' │ │ │ │ +8D7D1 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +8D7D5 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +8D7D9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8D7DB Length 000B (11) │ │ │ │ +8D7DD Version 01 (1) │ │ │ │ +8D7DE UID Size 04 (4) │ │ │ │ +8D7DF UID 00000000 (0) │ │ │ │ +8D7E3 GID Size 04 (4) │ │ │ │ +8D7E4 GID 00000000 (0) │ │ │ │ +8D7E8 PAYLOAD │ │ │ │ + │ │ │ │ +8F5F8 LOCAL HEADER #82 04034B50 (67324752) │ │ │ │ +8F5FC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +8F5FD Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8F5FE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8F600 Compression Method 0008 (8) 'Deflated' │ │ │ │ +8F602 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +8F606 CRC 76A08905 (1990232325) │ │ │ │ +8F60A Compressed Size 000029A6 (10662) │ │ │ │ +8F60E Uncompressed Size 0000D04F (53327) │ │ │ │ +8F612 Filename Length 001A (26) │ │ │ │ +8F614 Extra Length 001C (28) │ │ │ │ +8F616 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8F616: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +8F630 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +8F632 Length 0009 (9) │ │ │ │ +8F634 Flags 03 (3) 'Modification Access' │ │ │ │ +8F635 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +8F639 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +8F63D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8F63F Length 000B (11) │ │ │ │ +8F641 Version 01 (1) │ │ │ │ +8F642 UID Size 04 (4) │ │ │ │ +8F643 UID 00000000 (0) │ │ │ │ +8F647 GID Size 04 (4) │ │ │ │ +8F648 GID 00000000 (0) │ │ │ │ +8F64C PAYLOAD │ │ │ │ + │ │ │ │ +91FF2 LOCAL HEADER #83 04034B50 (67324752) │ │ │ │ +91FF6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +91FF7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +91FF8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +91FFA Compression Method 0008 (8) 'Deflated' │ │ │ │ +91FFC Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +92000 CRC BA402FE5 (3124768741) │ │ │ │ +92004 Compressed Size 000009AB (2475) │ │ │ │ +92008 Uncompressed Size 00001DB6 (7606) │ │ │ │ +9200C Filename Length 0018 (24) │ │ │ │ +9200E Extra Length 001C (28) │ │ │ │ +92010 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x92010: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +92028 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9202A Length 0009 (9) │ │ │ │ +9202C Flags 03 (3) 'Modification Access' │ │ │ │ +9202D Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +92031 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +92035 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +92037 Length 000B (11) │ │ │ │ +92039 Version 01 (1) │ │ │ │ +9203A UID Size 04 (4) │ │ │ │ +9203B UID 00000000 (0) │ │ │ │ +9203F GID Size 04 (4) │ │ │ │ +92040 GID 00000000 (0) │ │ │ │ +92044 PAYLOAD │ │ │ │ + │ │ │ │ +929EF LOCAL HEADER #84 04034B50 (67324752) │ │ │ │ +929F3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +929F4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +929F5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +929F7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +929F9 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +929FD CRC F0556E9A (4032130714) │ │ │ │ +92A01 Compressed Size 000152EE (86766) │ │ │ │ +92A05 Uncompressed Size 000159F8 (88568) │ │ │ │ +92A09 Filename Length 001E (30) │ │ │ │ +92A0B Extra Length 001C (28) │ │ │ │ +92A0D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x92A0D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +92A2B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +92A2D Length 0009 (9) │ │ │ │ +92A2F Flags 03 (3) 'Modification Access' │ │ │ │ +92A30 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +92A34 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +92A38 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +92A3A Length 000B (11) │ │ │ │ +92A3C Version 01 (1) │ │ │ │ +92A3D UID Size 04 (4) │ │ │ │ +92A3E UID 00000000 (0) │ │ │ │ +92A42 GID Size 04 (4) │ │ │ │ +92A43 GID 00000000 (0) │ │ │ │ +92A47 PAYLOAD │ │ │ │ + │ │ │ │ +A7D35 LOCAL HEADER #85 04034B50 (67324752) │ │ │ │ +A7D39 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A7D3A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A7D3B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A7D3D Compression Method 0008 (8) 'Deflated' │ │ │ │ +A7D3F Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +A7D43 CRC F5E2129F (4125233823) │ │ │ │ +A7D47 Compressed Size 000016BC (5820) │ │ │ │ +A7D4B Uncompressed Size 000016CD (5837) │ │ │ │ +A7D4F Filename Length 0015 (21) │ │ │ │ +A7D51 Extra Length 001C (28) │ │ │ │ +A7D53 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA7D53: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A7D68 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A7D6A Length 0009 (9) │ │ │ │ +A7D6C Flags 03 (3) 'Modification Access' │ │ │ │ +A7D6D Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +A7D71 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +A7D75 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A7D77 Length 000B (11) │ │ │ │ +A7D79 Version 01 (1) │ │ │ │ +A7D7A UID Size 04 (4) │ │ │ │ +A7D7B UID 00000000 (0) │ │ │ │ +A7D7F GID Size 04 (4) │ │ │ │ +A7D80 GID 00000000 (0) │ │ │ │ +A7D84 PAYLOAD │ │ │ │ + │ │ │ │ +A9440 LOCAL HEADER #86 04034B50 (67324752) │ │ │ │ +A9444 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A9445 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A9446 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A9448 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A944A Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +A944E CRC F5E2129F (4125233823) │ │ │ │ +A9452 Compressed Size 000016BC (5820) │ │ │ │ +A9456 Uncompressed Size 000016CD (5837) │ │ │ │ +A945A Filename Length 001C (28) │ │ │ │ +A945C Extra Length 001C (28) │ │ │ │ +A945E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA945E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A947A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A947C Length 0009 (9) │ │ │ │ +A947E Flags 03 (3) 'Modification Access' │ │ │ │ +A947F Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +A9483 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +A9487 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A9489 Length 000B (11) │ │ │ │ +A948B Version 01 (1) │ │ │ │ +A948C UID Size 04 (4) │ │ │ │ +A948D UID 00000000 (0) │ │ │ │ +A9491 GID Size 04 (4) │ │ │ │ +A9492 GID 00000000 (0) │ │ │ │ +A9496 PAYLOAD │ │ │ │ + │ │ │ │ +AAB52 LOCAL HEADER #87 04034B50 (67324752) │ │ │ │ +AAB56 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +AAB57 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +AAB58 General Purpose Flag 0000 (0) │ │ │ │ +AAB5A Compression Method 0000 (0) 'Stored' │ │ │ │ +AAB5C Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +AAB60 CRC FC95F24B (4237685323) │ │ │ │ +AAB64 Compressed Size 00001B84 (7044) │ │ │ │ +AAB68 Uncompressed Size 00001B84 (7044) │ │ │ │ +AAB6C Filename Length 0016 (22) │ │ │ │ +AAB6E Extra Length 001C (28) │ │ │ │ +AAB70 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xAAB70: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +AAB86 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +AAB88 Length 0009 (9) │ │ │ │ +AAB8A Flags 03 (3) 'Modification Access' │ │ │ │ +AAB8B Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +AAB8F Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +AAB93 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +AAB95 Length 000B (11) │ │ │ │ +AAB97 Version 01 (1) │ │ │ │ +AAB98 UID Size 04 (4) │ │ │ │ +AAB99 UID 00000000 (0) │ │ │ │ +AAB9D GID Size 04 (4) │ │ │ │ +AAB9E GID 00000000 (0) │ │ │ │ +AABA2 PAYLOAD │ │ │ │ + │ │ │ │ +AC726 LOCAL HEADER #88 04034B50 (67324752) │ │ │ │ +AC72A Extract Zip Spec 0A (10) '1.0' │ │ │ │ +AC72B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +AC72C General Purpose Flag 0000 (0) │ │ │ │ +AC72E Compression Method 0000 (0) 'Stored' │ │ │ │ +AC730 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +AC734 CRC D0D71F86 (3503759238) │ │ │ │ +AC738 Compressed Size 00000B7B (2939) │ │ │ │ +AC73C Uncompressed Size 00000B7B (2939) │ │ │ │ +AC740 Filename Length 0016 (22) │ │ │ │ +AC742 Extra Length 001C (28) │ │ │ │ +AC744 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xAC744: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +AC75A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +AC75C Length 0009 (9) │ │ │ │ +AC75E Flags 03 (3) 'Modification Access' │ │ │ │ +AC75F Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +AC763 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +AC767 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +AC769 Length 000B (11) │ │ │ │ +AC76B Version 01 (1) │ │ │ │ +AC76C UID Size 04 (4) │ │ │ │ +AC76D UID 00000000 (0) │ │ │ │ +AC771 GID Size 04 (4) │ │ │ │ +AC772 GID 00000000 (0) │ │ │ │ +AC776 PAYLOAD │ │ │ │ + │ │ │ │ +AD2F1 LOCAL HEADER #89 04034B50 (67324752) │ │ │ │ +AD2F5 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +AD2F6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +AD2F7 General Purpose Flag 0000 (0) │ │ │ │ +AD2F9 Compression Method 0000 (0) 'Stored' │ │ │ │ +AD2FB Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +AD2FF CRC FFF9C4D2 (4294558930) │ │ │ │ +AD303 Compressed Size 0000138F (5007) │ │ │ │ +AD307 Uncompressed Size 0000138F (5007) │ │ │ │ +AD30B Filename Length 0016 (22) │ │ │ │ +AD30D Extra Length 001C (28) │ │ │ │ +AD30F Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xAD30F: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +AD325 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +AD327 Length 0009 (9) │ │ │ │ +AD329 Flags 03 (3) 'Modification Access' │ │ │ │ +AD32A Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +AD32E Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +AD332 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +AD334 Length 000B (11) │ │ │ │ +AD336 Version 01 (1) │ │ │ │ +AD337 UID Size 04 (4) │ │ │ │ +AD338 UID 00000000 (0) │ │ │ │ +AD33C GID Size 04 (4) │ │ │ │ +AD33D GID 00000000 (0) │ │ │ │ +AD341 PAYLOAD │ │ │ │ + │ │ │ │ +AE6D0 LOCAL HEADER #90 04034B50 (67324752) │ │ │ │ +AE6D4 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +AE6D5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +AE6D6 General Purpose Flag 0000 (0) │ │ │ │ +AE6D8 Compression Method 0000 (0) 'Stored' │ │ │ │ +AE6DA Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +AE6DE CRC A1037E8E (2701360782) │ │ │ │ +AE6E2 Compressed Size 0000145E (5214) │ │ │ │ +AE6E6 Uncompressed Size 0000145E (5214) │ │ │ │ +AE6EA Filename Length 0016 (22) │ │ │ │ +AE6EC Extra Length 001C (28) │ │ │ │ +AE6EE Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xAE6EE: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +AE704 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +AE706 Length 0009 (9) │ │ │ │ +AE708 Flags 03 (3) 'Modification Access' │ │ │ │ +AE709 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +AE70D Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +AE711 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +AE713 Length 000B (11) │ │ │ │ +AE715 Version 01 (1) │ │ │ │ +AE716 UID Size 04 (4) │ │ │ │ +AE717 UID 00000000 (0) │ │ │ │ +AE71B GID Size 04 (4) │ │ │ │ +AE71C GID 00000000 (0) │ │ │ │ +AE720 PAYLOAD │ │ │ │ + │ │ │ │ +AFB7E LOCAL HEADER #91 04034B50 (67324752) │ │ │ │ +AFB82 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +AFB83 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +AFB84 General Purpose Flag 0000 (0) │ │ │ │ +AFB86 Compression Method 0000 (0) 'Stored' │ │ │ │ +AFB88 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +AFB8C CRC 5E9E64F1 (1587438833) │ │ │ │ +AFB90 Compressed Size 000008EC (2284) │ │ │ │ +AFB94 Uncompressed Size 000008EC (2284) │ │ │ │ +AFB98 Filename Length 0016 (22) │ │ │ │ +AFB9A Extra Length 001C (28) │ │ │ │ +AFB9C Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xAFB9C: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +AFBB2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +AFBB4 Length 0009 (9) │ │ │ │ +AFBB6 Flags 03 (3) 'Modification Access' │ │ │ │ +AFBB7 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +AFBBB Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +AFBBF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +AFBC1 Length 000B (11) │ │ │ │ +AFBC3 Version 01 (1) │ │ │ │ +AFBC4 UID Size 04 (4) │ │ │ │ +AFBC5 UID 00000000 (0) │ │ │ │ +AFBC9 GID Size 04 (4) │ │ │ │ +AFBCA GID 00000000 (0) │ │ │ │ +AFBCE PAYLOAD │ │ │ │ + │ │ │ │ +B04BA LOCAL HEADER #92 04034B50 (67324752) │ │ │ │ +B04BE Extract Zip Spec 0A (10) '1.0' │ │ │ │ +B04BF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B04C0 General Purpose Flag 0000 (0) │ │ │ │ +B04C2 Compression Method 0000 (0) 'Stored' │ │ │ │ +B04C4 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B04C8 CRC 42E340AB (1122189483) │ │ │ │ +B04CC Compressed Size 00001F2E (7982) │ │ │ │ +B04D0 Uncompressed Size 00001F2E (7982) │ │ │ │ +B04D4 Filename Length 001E (30) │ │ │ │ +B04D6 Extra Length 001C (28) │ │ │ │ +B04D8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB04D8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B04F6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B04F8 Length 0009 (9) │ │ │ │ +B04FA Flags 03 (3) 'Modification Access' │ │ │ │ +B04FB Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B04FF Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B0503 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B0505 Length 000B (11) │ │ │ │ +B0507 Version 01 (1) │ │ │ │ +B0508 UID Size 04 (4) │ │ │ │ +B0509 UID 00000000 (0) │ │ │ │ +B050D GID Size 04 (4) │ │ │ │ +B050E GID 00000000 (0) │ │ │ │ +B0512 PAYLOAD │ │ │ │ + │ │ │ │ +B2440 LOCAL HEADER #93 04034B50 (67324752) │ │ │ │ +B2444 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B2445 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B2446 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B2448 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B244A Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B244E CRC 2BF6ADFD (737586685) │ │ │ │ +B2452 Compressed Size 00003D74 (15732) │ │ │ │ +B2456 Uncompressed Size 0001664F (91727) │ │ │ │ +B245A Filename Length 001A (26) │ │ │ │ +B245C Extra Length 001C (28) │ │ │ │ +B245E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB245E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B2478 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B247A Length 0009 (9) │ │ │ │ +B247C Flags 03 (3) 'Modification Access' │ │ │ │ +B247D Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B2481 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B2485 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B2487 Length 000B (11) │ │ │ │ +B2489 Version 01 (1) │ │ │ │ +B248A UID Size 04 (4) │ │ │ │ +B248B UID 00000000 (0) │ │ │ │ +B248F GID Size 04 (4) │ │ │ │ +B2490 GID 00000000 (0) │ │ │ │ +B2494 PAYLOAD │ │ │ │ + │ │ │ │ +B6208 LOCAL HEADER #94 04034B50 (67324752) │ │ │ │ +B620C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B620D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B620E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B6210 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B6212 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B6216 CRC 67582E4E (1733832270) │ │ │ │ +B621A Compressed Size 000029CF (10703) │ │ │ │ +B621E Uncompressed Size 0000BB39 (47929) │ │ │ │ +B6222 Filename Length 0018 (24) │ │ │ │ +B6224 Extra Length 001C (28) │ │ │ │ +B6226 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB6226: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B623E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B6240 Length 0009 (9) │ │ │ │ +B6242 Flags 03 (3) 'Modification Access' │ │ │ │ +B6243 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B6247 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B624B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B624D Length 000B (11) │ │ │ │ +B624F Version 01 (1) │ │ │ │ +B6250 UID Size 04 (4) │ │ │ │ +B6251 UID 00000000 (0) │ │ │ │ +B6255 GID Size 04 (4) │ │ │ │ +B6256 GID 00000000 (0) │ │ │ │ +B625A PAYLOAD │ │ │ │ + │ │ │ │ +B8C29 LOCAL HEADER #95 04034B50 (67324752) │ │ │ │ +B8C2D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8C2E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8C2F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8C31 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8C33 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B8C37 CRC DCB3B516 (3702764822) │ │ │ │ +B8C3B Compressed Size 000000AE (174) │ │ │ │ +B8C3F Uncompressed Size 000000FC (252) │ │ │ │ +B8C43 Filename Length 0016 (22) │ │ │ │ +B8C45 Extra Length 001C (28) │ │ │ │ +B8C47 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8C47: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8C5D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8C5F Length 0009 (9) │ │ │ │ +B8C61 Flags 03 (3) 'Modification Access' │ │ │ │ +B8C62 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B8C66 Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B8C6A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8C6C Length 000B (11) │ │ │ │ +B8C6E Version 01 (1) │ │ │ │ +B8C6F UID Size 04 (4) │ │ │ │ +B8C70 UID 00000000 (0) │ │ │ │ +B8C74 GID Size 04 (4) │ │ │ │ +B8C75 GID 00000000 (0) │ │ │ │ +B8C79 PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ │ │ │ │ -B8CFE LOCAL HEADER #96 04034B50 (67324752) │ │ │ │ -B8D02 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8D03 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8D04 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8D06 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8D08 Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -B8D0C CRC 58439733 (1480824627) │ │ │ │ -B8D10 Compressed Size 00000077 (119) │ │ │ │ -B8D14 Uncompressed Size 000000A2 (162) │ │ │ │ -B8D18 Filename Length 002D (45) │ │ │ │ -B8D1A Extra Length 001C (28) │ │ │ │ -B8D1C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8D1C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8D49 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8D4B Length 0009 (9) │ │ │ │ -B8D4D Flags 03 (3) 'Modification Access' │ │ │ │ -B8D4E Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -B8D52 Access Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -B8D56 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8D58 Length 000B (11) │ │ │ │ -B8D5A Version 01 (1) │ │ │ │ -B8D5B UID Size 04 (4) │ │ │ │ -B8D5C UID 00000000 (0) │ │ │ │ -B8D60 GID Size 04 (4) │ │ │ │ -B8D61 GID 00000000 (0) │ │ │ │ -B8D65 PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ - │ │ │ │ -B8DDC CENTRAL HEADER #1 02014B50 (33639248) │ │ │ │ -B8DE0 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8DE1 Created OS 03 (3) 'Unix' │ │ │ │ -B8DE2 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -B8DE3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8DE4 General Purpose Flag 0000 (0) │ │ │ │ -B8DE6 Compression Method 0000 (0) 'Stored' │ │ │ │ -B8DE8 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B8DEC CRC 2CAB616F (749429103) │ │ │ │ -B8DF0 Compressed Size 00000014 (20) │ │ │ │ -B8DF4 Uncompressed Size 00000014 (20) │ │ │ │ -B8DF8 Filename Length 0008 (8) │ │ │ │ -B8DFA Extra Length 0018 (24) │ │ │ │ -B8DFC Comment Length 0000 (0) │ │ │ │ -B8DFE Disk Start 0000 (0) │ │ │ │ -B8E00 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8E02 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8E06 Local Header Offset 00000000 (0) │ │ │ │ -B8E0A Filename 'XXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8E0A: Filename 'XXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8E12 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8E14 Length 0005 (5) │ │ │ │ -B8E16 Flags 01 (1) 'Modification' │ │ │ │ -B8E17 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B8E1B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8E1D Length 000B (11) │ │ │ │ -B8E1F Version 01 (1) │ │ │ │ -B8E20 UID Size 04 (4) │ │ │ │ -B8E21 UID 00000000 (0) │ │ │ │ -B8E25 GID Size 04 (4) │ │ │ │ -B8E26 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8E2A CENTRAL HEADER #2 02014B50 (33639248) │ │ │ │ -B8E2E Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8E2F Created OS 03 (3) 'Unix' │ │ │ │ -B8E30 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8E31 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8E32 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8E34 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8E36 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B8E3A CRC 375ECD24 (928959780) │ │ │ │ -B8E3E Compressed Size 00000D22 (3362) │ │ │ │ -B8E42 Uncompressed Size 00003933 (14643) │ │ │ │ -B8E46 Filename Length 001B (27) │ │ │ │ -B8E48 Extra Length 0018 (24) │ │ │ │ -B8E4A Comment Length 0000 (0) │ │ │ │ -B8E4C Disk Start 0000 (0) │ │ │ │ -B8E4E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8E50 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8E54 Local Header Offset 00000056 (86) │ │ │ │ -B8E58 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8E58: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8E73 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8E75 Length 0005 (5) │ │ │ │ -B8E77 Flags 01 (1) 'Modification' │ │ │ │ -B8E78 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B8E7C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8E7E Length 000B (11) │ │ │ │ -B8E80 Version 01 (1) │ │ │ │ -B8E81 UID Size 04 (4) │ │ │ │ -B8E82 UID 00000000 (0) │ │ │ │ -B8E86 GID Size 04 (4) │ │ │ │ -B8E87 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8E8B CENTRAL HEADER #3 02014B50 (33639248) │ │ │ │ -B8E8F Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8E90 Created OS 03 (3) 'Unix' │ │ │ │ -B8E91 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8E92 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8E93 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8E95 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8E97 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B8E9B CRC 986340FB (2556641531) │ │ │ │ -B8E9F Compressed Size 000015AF (5551) │ │ │ │ -B8EA3 Uncompressed Size 00004602 (17922) │ │ │ │ -B8EA7 Filename Length 0014 (20) │ │ │ │ -B8EA9 Extra Length 0018 (24) │ │ │ │ -B8EAB Comment Length 0000 (0) │ │ │ │ -B8EAD Disk Start 0000 (0) │ │ │ │ -B8EAF Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8EB1 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8EB5 Local Header Offset 00000DCD (3533) │ │ │ │ -B8EB9 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8EB9: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8ECD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8ECF Length 0005 (5) │ │ │ │ -B8ED1 Flags 01 (1) 'Modification' │ │ │ │ -B8ED2 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B8ED6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8ED8 Length 000B (11) │ │ │ │ -B8EDA Version 01 (1) │ │ │ │ -B8EDB UID Size 04 (4) │ │ │ │ -B8EDC UID 00000000 (0) │ │ │ │ -B8EE0 GID Size 04 (4) │ │ │ │ -B8EE1 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8EE5 CENTRAL HEADER #4 02014B50 (33639248) │ │ │ │ -B8EE9 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8EEA Created OS 03 (3) 'Unix' │ │ │ │ -B8EEB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8EEC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8EED General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8EEF Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8EF1 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B8EF5 CRC 911C5DC4 (2434555332) │ │ │ │ -B8EF9 Compressed Size 000006D3 (1747) │ │ │ │ -B8EFD Uncompressed Size 00001241 (4673) │ │ │ │ -B8F01 Filename Length 0013 (19) │ │ │ │ -B8F03 Extra Length 0018 (24) │ │ │ │ -B8F05 Comment Length 0000 (0) │ │ │ │ -B8F07 Disk Start 0000 (0) │ │ │ │ -B8F09 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8F0B Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8F0F Local Header Offset 000023CA (9162) │ │ │ │ -B8F13 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8F13: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8F26 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8F28 Length 0005 (5) │ │ │ │ -B8F2A Flags 01 (1) 'Modification' │ │ │ │ -B8F2B Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B8F2F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8F31 Length 000B (11) │ │ │ │ -B8F33 Version 01 (1) │ │ │ │ -B8F34 UID Size 04 (4) │ │ │ │ -B8F35 UID 00000000 (0) │ │ │ │ -B8F39 GID Size 04 (4) │ │ │ │ -B8F3A GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8F3E CENTRAL HEADER #5 02014B50 (33639248) │ │ │ │ -B8F42 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8F43 Created OS 03 (3) 'Unix' │ │ │ │ -B8F44 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8F45 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8F46 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8F48 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8F4A Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B8F4E CRC CE3C2FCC (3460050892) │ │ │ │ -B8F52 Compressed Size 00002E6E (11886) │ │ │ │ -B8F56 Uncompressed Size 0000D4B3 (54451) │ │ │ │ -B8F5A Filename Length 0014 (20) │ │ │ │ -B8F5C Extra Length 0018 (24) │ │ │ │ -B8F5E Comment Length 0000 (0) │ │ │ │ -B8F60 Disk Start 0000 (0) │ │ │ │ -B8F62 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8F64 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8F68 Local Header Offset 00002AEA (10986) │ │ │ │ -B8F6C Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8F6C: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8F80 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8F82 Length 0005 (5) │ │ │ │ -B8F84 Flags 01 (1) 'Modification' │ │ │ │ -B8F85 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B8F89 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8F8B Length 000B (11) │ │ │ │ -B8F8D Version 01 (1) │ │ │ │ -B8F8E UID Size 04 (4) │ │ │ │ -B8F8F UID 00000000 (0) │ │ │ │ -B8F93 GID Size 04 (4) │ │ │ │ -B8F94 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8F98 CENTRAL HEADER #6 02014B50 (33639248) │ │ │ │ -B8F9C Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8F9D Created OS 03 (3) 'Unix' │ │ │ │ -B8F9E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8F9F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8FA0 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8FA2 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8FA4 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B8FA8 CRC A85875F8 (2824369656) │ │ │ │ -B8FAC Compressed Size 000003F0 (1008) │ │ │ │ -B8FB0 Uncompressed Size 00000876 (2166) │ │ │ │ -B8FB4 Filename Length 0014 (20) │ │ │ │ -B8FB6 Extra Length 0018 (24) │ │ │ │ -B8FB8 Comment Length 0000 (0) │ │ │ │ -B8FBA Disk Start 0000 (0) │ │ │ │ -B8FBC Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B8FBE Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B8FC2 Local Header Offset 000059A6 (22950) │ │ │ │ -B8FC6 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB8FC6: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B8FDA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B8FDC Length 0005 (5) │ │ │ │ -B8FDE Flags 01 (1) 'Modification' │ │ │ │ -B8FDF Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B8FE3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B8FE5 Length 000B (11) │ │ │ │ -B8FE7 Version 01 (1) │ │ │ │ -B8FE8 UID Size 04 (4) │ │ │ │ -B8FE9 UID 00000000 (0) │ │ │ │ -B8FED GID Size 04 (4) │ │ │ │ -B8FEE GID 00000000 (0) │ │ │ │ - │ │ │ │ -B8FF2 CENTRAL HEADER #7 02014B50 (33639248) │ │ │ │ -B8FF6 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B8FF7 Created OS 03 (3) 'Unix' │ │ │ │ -B8FF8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B8FF9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B8FFA General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B8FFC Compression Method 0008 (8) 'Deflated' │ │ │ │ -B8FFE Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9002 CRC F88D1943 (4169996611) │ │ │ │ -B9006 Compressed Size 000001AD (429) │ │ │ │ -B900A Uncompressed Size 000002FC (764) │ │ │ │ -B900E Filename Length 0011 (17) │ │ │ │ -B9010 Extra Length 0018 (24) │ │ │ │ -B9012 Comment Length 0000 (0) │ │ │ │ -B9014 Disk Start 0000 (0) │ │ │ │ -B9016 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9018 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B901C Local Header Offset 00005DE4 (24036) │ │ │ │ -B9020 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9020: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9031 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9033 Length 0005 (5) │ │ │ │ -B9035 Flags 01 (1) 'Modification' │ │ │ │ -B9036 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B903A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B903C Length 000B (11) │ │ │ │ -B903E Version 01 (1) │ │ │ │ -B903F UID Size 04 (4) │ │ │ │ -B9040 UID 00000000 (0) │ │ │ │ -B9044 GID Size 04 (4) │ │ │ │ -B9045 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9049 CENTRAL HEADER #8 02014B50 (33639248) │ │ │ │ -B904D Created Zip Spec 3D (61) '6.1' │ │ │ │ -B904E Created OS 03 (3) 'Unix' │ │ │ │ -B904F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9050 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9051 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9053 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9055 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9059 CRC B6182455 (3055035477) │ │ │ │ -B905D Compressed Size 000020C3 (8387) │ │ │ │ -B9061 Uncompressed Size 0000B4B0 (46256) │ │ │ │ -B9065 Filename Length 001B (27) │ │ │ │ -B9067 Extra Length 0018 (24) │ │ │ │ -B9069 Comment Length 0000 (0) │ │ │ │ -B906B Disk Start 0000 (0) │ │ │ │ -B906D Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B906F Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9073 Local Header Offset 00005FDC (24540) │ │ │ │ -B9077 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9077: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9092 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9094 Length 0005 (5) │ │ │ │ -B9096 Flags 01 (1) 'Modification' │ │ │ │ -B9097 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B909B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B909D Length 000B (11) │ │ │ │ -B909F Version 01 (1) │ │ │ │ -B90A0 UID Size 04 (4) │ │ │ │ -B90A1 UID 00000000 (0) │ │ │ │ -B90A5 GID Size 04 (4) │ │ │ │ -B90A6 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B90AA CENTRAL HEADER #9 02014B50 (33639248) │ │ │ │ -B90AE Created Zip Spec 3D (61) '6.1' │ │ │ │ -B90AF Created OS 03 (3) 'Unix' │ │ │ │ -B90B0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B90B1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B90B2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B90B4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B90B6 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B90BA CRC 93C4F0D6 (2479157462) │ │ │ │ -B90BE Compressed Size 00000E65 (3685) │ │ │ │ -B90C2 Uncompressed Size 00003092 (12434) │ │ │ │ -B90C6 Filename Length 001D (29) │ │ │ │ -B90C8 Extra Length 0018 (24) │ │ │ │ -B90CA Comment Length 0000 (0) │ │ │ │ -B90CC Disk Start 0000 (0) │ │ │ │ -B90CE Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B90D0 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B90D4 Local Header Offset 000080F4 (33012) │ │ │ │ -B90D8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB90D8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B90F5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B90F7 Length 0005 (5) │ │ │ │ -B90F9 Flags 01 (1) 'Modification' │ │ │ │ -B90FA Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B90FE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9100 Length 000B (11) │ │ │ │ -B9102 Version 01 (1) │ │ │ │ -B9103 UID Size 04 (4) │ │ │ │ -B9104 UID 00000000 (0) │ │ │ │ -B9108 GID Size 04 (4) │ │ │ │ -B9109 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B910D CENTRAL HEADER #10 02014B50 (33639248) │ │ │ │ -B9111 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9112 Created OS 03 (3) 'Unix' │ │ │ │ -B9113 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9114 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9115 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9117 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9119 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B911D CRC 4F204BB6 (1327516598) │ │ │ │ -B9121 Compressed Size 00000994 (2452) │ │ │ │ -B9125 Uncompressed Size 00001D3D (7485) │ │ │ │ -B9129 Filename Length 0019 (25) │ │ │ │ -B912B Extra Length 0018 (24) │ │ │ │ -B912D Comment Length 0000 (0) │ │ │ │ -B912F Disk Start 0000 (0) │ │ │ │ -B9131 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9133 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9137 Local Header Offset 00008FB0 (36784) │ │ │ │ -B913B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB913B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9154 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9156 Length 0005 (5) │ │ │ │ -B9158 Flags 01 (1) 'Modification' │ │ │ │ -B9159 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B915D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B915F Length 000B (11) │ │ │ │ -B9161 Version 01 (1) │ │ │ │ -B9162 UID Size 04 (4) │ │ │ │ -B9163 UID 00000000 (0) │ │ │ │ -B9167 GID Size 04 (4) │ │ │ │ -B9168 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B916C CENTRAL HEADER #11 02014B50 (33639248) │ │ │ │ -B9170 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9171 Created OS 03 (3) 'Unix' │ │ │ │ -B9172 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9173 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9174 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9176 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9178 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B917C CRC 40C870BB (1086877883) │ │ │ │ -B9180 Compressed Size 0000387B (14459) │ │ │ │ -B9184 Uncompressed Size 0000F7F4 (63476) │ │ │ │ -B9188 Filename Length 0015 (21) │ │ │ │ -B918A Extra Length 0018 (24) │ │ │ │ -B918C Comment Length 0000 (0) │ │ │ │ -B918E Disk Start 0000 (0) │ │ │ │ -B9190 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9192 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9196 Local Header Offset 00009997 (39319) │ │ │ │ -B919A Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB919A: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B91AF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B91B1 Length 0005 (5) │ │ │ │ -B91B3 Flags 01 (1) 'Modification' │ │ │ │ -B91B4 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B91B8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B91BA Length 000B (11) │ │ │ │ -B91BC Version 01 (1) │ │ │ │ -B91BD UID Size 04 (4) │ │ │ │ -B91BE UID 00000000 (0) │ │ │ │ -B91C2 GID Size 04 (4) │ │ │ │ -B91C3 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B91C7 CENTRAL HEADER #12 02014B50 (33639248) │ │ │ │ -B91CB Created Zip Spec 3D (61) '6.1' │ │ │ │ -B91CC Created OS 03 (3) 'Unix' │ │ │ │ -B91CD Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B91CE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B91CF General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B91D1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B91D3 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B91D7 CRC 5DFD1C6A (1576868970) │ │ │ │ -B91DB Compressed Size 0000AB00 (43776) │ │ │ │ -B91DF Uncompressed Size 0003E0D3 (254163) │ │ │ │ -B91E3 Filename Length 0012 (18) │ │ │ │ -B91E5 Extra Length 0018 (24) │ │ │ │ -B91E7 Comment Length 0000 (0) │ │ │ │ -B91E9 Disk Start 0000 (0) │ │ │ │ -B91EB Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B91ED Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B91F1 Local Header Offset 0000D261 (53857) │ │ │ │ -B91F5 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB91F5: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9207 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9209 Length 0005 (5) │ │ │ │ -B920B Flags 01 (1) 'Modification' │ │ │ │ -B920C Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9210 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9212 Length 000B (11) │ │ │ │ -B9214 Version 01 (1) │ │ │ │ -B9215 UID Size 04 (4) │ │ │ │ -B9216 UID 00000000 (0) │ │ │ │ -B921A GID Size 04 (4) │ │ │ │ -B921B GID 00000000 (0) │ │ │ │ - │ │ │ │ -B921F CENTRAL HEADER #13 02014B50 (33639248) │ │ │ │ -B9223 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9224 Created OS 03 (3) 'Unix' │ │ │ │ -B9225 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9226 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9227 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9229 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B922B Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B922F CRC 228DA013 (579706899) │ │ │ │ -B9233 Compressed Size 00003B0E (15118) │ │ │ │ -B9237 Uncompressed Size 0001B46C (111724) │ │ │ │ -B923B Filename Length 0015 (21) │ │ │ │ -B923D Extra Length 0018 (24) │ │ │ │ -B923F Comment Length 0000 (0) │ │ │ │ -B9241 Disk Start 0000 (0) │ │ │ │ -B9243 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9245 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9249 Local Header Offset 00017DAD (97709) │ │ │ │ -B924D Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB924D: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9262 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9264 Length 0005 (5) │ │ │ │ -B9266 Flags 01 (1) 'Modification' │ │ │ │ -B9267 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B926B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B926D Length 000B (11) │ │ │ │ -B926F Version 01 (1) │ │ │ │ -B9270 UID Size 04 (4) │ │ │ │ -B9271 UID 00000000 (0) │ │ │ │ -B9275 GID Size 04 (4) │ │ │ │ -B9276 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B927A CENTRAL HEADER #14 02014B50 (33639248) │ │ │ │ -B927E Created Zip Spec 3D (61) '6.1' │ │ │ │ -B927F Created OS 03 (3) 'Unix' │ │ │ │ -B9280 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9281 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9282 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9284 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9286 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B928A CRC B8D3D7C9 (3100891081) │ │ │ │ -B928E Compressed Size 0000919D (37277) │ │ │ │ -B9292 Uncompressed Size 0003D5E9 (251369) │ │ │ │ -B9296 Filename Length 0014 (20) │ │ │ │ -B9298 Extra Length 0018 (24) │ │ │ │ -B929A Comment Length 0000 (0) │ │ │ │ -B929C Disk Start 0000 (0) │ │ │ │ -B929E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B92A0 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B92A4 Local Header Offset 0001B90A (112906) │ │ │ │ -B92A8 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB92A8: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B92BC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B92BE Length 0005 (5) │ │ │ │ -B92C0 Flags 01 (1) 'Modification' │ │ │ │ -B92C1 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B92C5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B92C7 Length 000B (11) │ │ │ │ -B92C9 Version 01 (1) │ │ │ │ -B92CA UID Size 04 (4) │ │ │ │ -B92CB UID 00000000 (0) │ │ │ │ -B92CF GID Size 04 (4) │ │ │ │ -B92D0 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B92D4 CENTRAL HEADER #15 02014B50 (33639248) │ │ │ │ -B92D8 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B92D9 Created OS 03 (3) 'Unix' │ │ │ │ -B92DA Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B92DB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B92DC General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B92DE Compression Method 0008 (8) 'Deflated' │ │ │ │ -B92E0 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B92E4 CRC 9D665911 (2640730385) │ │ │ │ -B92E8 Compressed Size 00001219 (4633) │ │ │ │ -B92EC Uncompressed Size 00003C91 (15505) │ │ │ │ -B92F0 Filename Length 0010 (16) │ │ │ │ -B92F2 Extra Length 0018 (24) │ │ │ │ -B92F4 Comment Length 0000 (0) │ │ │ │ -B92F6 Disk Start 0000 (0) │ │ │ │ -B92F8 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B92FA Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B92FE Local Header Offset 00024AF5 (150261) │ │ │ │ -B9302 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9302: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9312 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9314 Length 0005 (5) │ │ │ │ -B9316 Flags 01 (1) 'Modification' │ │ │ │ -B9317 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B931B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B931D Length 000B (11) │ │ │ │ -B931F Version 01 (1) │ │ │ │ -B9320 UID Size 04 (4) │ │ │ │ -B9321 UID 00000000 (0) │ │ │ │ -B9325 GID Size 04 (4) │ │ │ │ -B9326 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B932A CENTRAL HEADER #16 02014B50 (33639248) │ │ │ │ -B932E Created Zip Spec 3D (61) '6.1' │ │ │ │ -B932F Created OS 03 (3) 'Unix' │ │ │ │ -B9330 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9331 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9332 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9334 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9336 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B933A CRC 8B0053E7 (2332054503) │ │ │ │ -B933E Compressed Size 00002A5F (10847) │ │ │ │ -B9342 Uncompressed Size 0001151F (70943) │ │ │ │ -B9346 Filename Length 0016 (22) │ │ │ │ -B9348 Extra Length 0018 (24) │ │ │ │ -B934A Comment Length 0000 (0) │ │ │ │ -B934C Disk Start 0000 (0) │ │ │ │ -B934E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9350 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9354 Local Header Offset 00025D58 (154968) │ │ │ │ -B9358 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9358: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B936E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9370 Length 0005 (5) │ │ │ │ -B9372 Flags 01 (1) 'Modification' │ │ │ │ -B9373 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9377 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9379 Length 000B (11) │ │ │ │ -B937B Version 01 (1) │ │ │ │ -B937C UID Size 04 (4) │ │ │ │ -B937D UID 00000000 (0) │ │ │ │ -B9381 GID Size 04 (4) │ │ │ │ -B9382 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9386 CENTRAL HEADER #17 02014B50 (33639248) │ │ │ │ -B938A Created Zip Spec 3D (61) '6.1' │ │ │ │ -B938B Created OS 03 (3) 'Unix' │ │ │ │ -B938C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B938D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B938E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9390 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9392 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9396 CRC 103DEDE1 (272494049) │ │ │ │ -B939A Compressed Size 000014D8 (5336) │ │ │ │ -B939E Uncompressed Size 0000518D (20877) │ │ │ │ -B93A2 Filename Length 001D (29) │ │ │ │ -B93A4 Extra Length 0018 (24) │ │ │ │ -B93A6 Comment Length 0000 (0) │ │ │ │ -B93A8 Disk Start 0000 (0) │ │ │ │ -B93AA Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B93AC Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B93B0 Local Header Offset 00028807 (165895) │ │ │ │ -B93B4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB93B4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B93D1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B93D3 Length 0005 (5) │ │ │ │ -B93D5 Flags 01 (1) 'Modification' │ │ │ │ -B93D6 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B93DA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B93DC Length 000B (11) │ │ │ │ -B93DE Version 01 (1) │ │ │ │ -B93DF UID Size 04 (4) │ │ │ │ -B93E0 UID 00000000 (0) │ │ │ │ -B93E4 GID Size 04 (4) │ │ │ │ -B93E5 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B93E9 CENTRAL HEADER #18 02014B50 (33639248) │ │ │ │ -B93ED Created Zip Spec 3D (61) '6.1' │ │ │ │ -B93EE Created OS 03 (3) 'Unix' │ │ │ │ -B93EF Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B93F0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B93F1 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B93F3 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B93F5 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B93F9 CRC C2725354 (3262272340) │ │ │ │ -B93FD Compressed Size 0000380E (14350) │ │ │ │ -B9401 Uncompressed Size 0000EA4C (59980) │ │ │ │ -B9405 Filename Length 001C (28) │ │ │ │ -B9407 Extra Length 0018 (24) │ │ │ │ -B9409 Comment Length 0000 (0) │ │ │ │ -B940B Disk Start 0000 (0) │ │ │ │ -B940D Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B940F Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9413 Local Header Offset 00029D36 (171318) │ │ │ │ -B9417 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9417: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9433 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9435 Length 0005 (5) │ │ │ │ -B9437 Flags 01 (1) 'Modification' │ │ │ │ -B9438 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B943C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B943E Length 000B (11) │ │ │ │ -B9440 Version 01 (1) │ │ │ │ -B9441 UID Size 04 (4) │ │ │ │ -B9442 UID 00000000 (0) │ │ │ │ -B9446 GID Size 04 (4) │ │ │ │ -B9447 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B944B CENTRAL HEADER #19 02014B50 (33639248) │ │ │ │ -B944F Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9450 Created OS 03 (3) 'Unix' │ │ │ │ -B9451 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9452 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9453 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9455 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9457 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B945B CRC 2E09C12B (772391211) │ │ │ │ -B945F Compressed Size 000006A2 (1698) │ │ │ │ -B9463 Uncompressed Size 000011F4 (4596) │ │ │ │ -B9467 Filename Length 001C (28) │ │ │ │ -B9469 Extra Length 0018 (24) │ │ │ │ -B946B Comment Length 0000 (0) │ │ │ │ -B946D Disk Start 0000 (0) │ │ │ │ -B946F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9471 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9475 Local Header Offset 0002D59A (185754) │ │ │ │ -B9479 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9479: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9495 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9497 Length 0005 (5) │ │ │ │ -B9499 Flags 01 (1) 'Modification' │ │ │ │ -B949A Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B949E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B94A0 Length 000B (11) │ │ │ │ -B94A2 Version 01 (1) │ │ │ │ -B94A3 UID Size 04 (4) │ │ │ │ -B94A4 UID 00000000 (0) │ │ │ │ -B94A8 GID Size 04 (4) │ │ │ │ -B94A9 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B94AD CENTRAL HEADER #20 02014B50 (33639248) │ │ │ │ -B94B1 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B94B2 Created OS 03 (3) 'Unix' │ │ │ │ -B94B3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B94B4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B94B5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B94B7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B94B9 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B94BD CRC 9FDEA958 (2682169688) │ │ │ │ -B94C1 Compressed Size 0000107D (4221) │ │ │ │ -B94C5 Uncompressed Size 00004BFF (19455) │ │ │ │ -B94C9 Filename Length 001B (27) │ │ │ │ -B94CB Extra Length 0018 (24) │ │ │ │ -B94CD Comment Length 0000 (0) │ │ │ │ -B94CF Disk Start 0000 (0) │ │ │ │ -B94D1 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B94D3 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B94D7 Local Header Offset 0002DC92 (187538) │ │ │ │ -B94DB Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB94DB: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B94F6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B94F8 Length 0005 (5) │ │ │ │ -B94FA Flags 01 (1) 'Modification' │ │ │ │ -B94FB Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B94FF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9501 Length 000B (11) │ │ │ │ -B9503 Version 01 (1) │ │ │ │ -B9504 UID Size 04 (4) │ │ │ │ -B9505 UID 00000000 (0) │ │ │ │ -B9509 GID Size 04 (4) │ │ │ │ -B950A GID 00000000 (0) │ │ │ │ - │ │ │ │ -B950E CENTRAL HEADER #21 02014B50 (33639248) │ │ │ │ -B9512 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9513 Created OS 03 (3) 'Unix' │ │ │ │ -B9514 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9515 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9516 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9518 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B951A Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B951E CRC EDE5C0BB (3991257275) │ │ │ │ -B9522 Compressed Size 00003BDA (15322) │ │ │ │ -B9526 Uncompressed Size 0000D783 (55171) │ │ │ │ -B952A Filename Length 001D (29) │ │ │ │ -B952C Extra Length 0018 (24) │ │ │ │ -B952E Comment Length 0000 (0) │ │ │ │ -B9530 Disk Start 0000 (0) │ │ │ │ -B9532 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9534 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9538 Local Header Offset 0002ED64 (191844) │ │ │ │ -B953C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB953C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9559 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B955B Length 0005 (5) │ │ │ │ -B955D Flags 01 (1) 'Modification' │ │ │ │ -B955E Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9562 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9564 Length 000B (11) │ │ │ │ -B9566 Version 01 (1) │ │ │ │ -B9567 UID Size 04 (4) │ │ │ │ -B9568 UID 00000000 (0) │ │ │ │ -B956C GID Size 04 (4) │ │ │ │ -B956D GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9571 CENTRAL HEADER #22 02014B50 (33639248) │ │ │ │ -B9575 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9576 Created OS 03 (3) 'Unix' │ │ │ │ -B9577 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9578 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9579 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B957B Compression Method 0008 (8) 'Deflated' │ │ │ │ -B957D Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9581 CRC 9FDCAE48 (2682039880) │ │ │ │ -B9585 Compressed Size 00000D6B (3435) │ │ │ │ -B9589 Uncompressed Size 0000388D (14477) │ │ │ │ -B958D Filename Length 001D (29) │ │ │ │ -B958F Extra Length 0018 (24) │ │ │ │ -B9591 Comment Length 0000 (0) │ │ │ │ -B9593 Disk Start 0000 (0) │ │ │ │ -B9595 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9597 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B959B Local Header Offset 00032995 (207253) │ │ │ │ -B959F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB959F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B95BC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B95BE Length 0005 (5) │ │ │ │ -B95C0 Flags 01 (1) 'Modification' │ │ │ │ -B95C1 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B95C5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B95C7 Length 000B (11) │ │ │ │ -B95C9 Version 01 (1) │ │ │ │ -B95CA UID Size 04 (4) │ │ │ │ -B95CB UID 00000000 (0) │ │ │ │ -B95CF GID Size 04 (4) │ │ │ │ -B95D0 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B95D4 CENTRAL HEADER #23 02014B50 (33639248) │ │ │ │ -B95D8 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B95D9 Created OS 03 (3) 'Unix' │ │ │ │ -B95DA Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B95DB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B95DC General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B95DE Compression Method 0008 (8) 'Deflated' │ │ │ │ -B95E0 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B95E4 CRC 009EB783 (10401667) │ │ │ │ -B95E8 Compressed Size 00001C6A (7274) │ │ │ │ -B95EC Uncompressed Size 0000C186 (49542) │ │ │ │ -B95F0 Filename Length 001A (26) │ │ │ │ -B95F2 Extra Length 0018 (24) │ │ │ │ -B95F4 Comment Length 0000 (0) │ │ │ │ -B95F6 Disk Start 0000 (0) │ │ │ │ -B95F8 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B95FA Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B95FE Local Header Offset 00033757 (210775) │ │ │ │ -B9602 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9602: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B961C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B961E Length 0005 (5) │ │ │ │ -B9620 Flags 01 (1) 'Modification' │ │ │ │ -B9621 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9625 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9627 Length 000B (11) │ │ │ │ -B9629 Version 01 (1) │ │ │ │ -B962A UID Size 04 (4) │ │ │ │ -B962B UID 00000000 (0) │ │ │ │ -B962F GID Size 04 (4) │ │ │ │ -B9630 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9634 CENTRAL HEADER #24 02014B50 (33639248) │ │ │ │ -B9638 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9639 Created OS 03 (3) 'Unix' │ │ │ │ -B963A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B963B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B963C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B963E Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9640 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9644 CRC 04F1FDC5 (82968005) │ │ │ │ -B9648 Compressed Size 000003DF (991) │ │ │ │ -B964C Uncompressed Size 00000935 (2357) │ │ │ │ -B9650 Filename Length 0012 (18) │ │ │ │ -B9652 Extra Length 0018 (24) │ │ │ │ -B9654 Comment Length 0000 (0) │ │ │ │ -B9656 Disk Start 0000 (0) │ │ │ │ -B9658 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B965A Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B965E Local Header Offset 00035415 (218133) │ │ │ │ -B9662 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9662: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9674 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9676 Length 0005 (5) │ │ │ │ -B9678 Flags 01 (1) 'Modification' │ │ │ │ -B9679 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B967D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B967F Length 000B (11) │ │ │ │ -B9681 Version 01 (1) │ │ │ │ -B9682 UID Size 04 (4) │ │ │ │ -B9683 UID 00000000 (0) │ │ │ │ -B9687 GID Size 04 (4) │ │ │ │ -B9688 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B968C CENTRAL HEADER #25 02014B50 (33639248) │ │ │ │ -B9690 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9691 Created OS 03 (3) 'Unix' │ │ │ │ -B9692 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9693 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9694 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9696 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9698 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B969C CRC C605FB96 (3322280854) │ │ │ │ -B96A0 Compressed Size 000001D3 (467) │ │ │ │ -B96A4 Uncompressed Size 00000311 (785) │ │ │ │ -B96A8 Filename Length 0020 (32) │ │ │ │ -B96AA Extra Length 0018 (24) │ │ │ │ -B96AC Comment Length 0000 (0) │ │ │ │ -B96AE Disk Start 0000 (0) │ │ │ │ -B96B0 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B96B2 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B96B6 Local Header Offset 00035840 (219200) │ │ │ │ -B96BA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB96BA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B96DA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B96DC Length 0005 (5) │ │ │ │ -B96DE Flags 01 (1) 'Modification' │ │ │ │ -B96DF Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B96E3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B96E5 Length 000B (11) │ │ │ │ -B96E7 Version 01 (1) │ │ │ │ -B96E8 UID Size 04 (4) │ │ │ │ -B96E9 UID 00000000 (0) │ │ │ │ -B96ED GID Size 04 (4) │ │ │ │ -B96EE GID 00000000 (0) │ │ │ │ - │ │ │ │ -B96F2 CENTRAL HEADER #26 02014B50 (33639248) │ │ │ │ -B96F6 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B96F7 Created OS 03 (3) 'Unix' │ │ │ │ -B96F8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B96F9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B96FA General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B96FC Compression Method 0008 (8) 'Deflated' │ │ │ │ -B96FE Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9702 CRC 3202CA1B (839043611) │ │ │ │ -B9706 Compressed Size 000017AA (6058) │ │ │ │ -B970A Uncompressed Size 00009D18 (40216) │ │ │ │ -B970E Filename Length 001B (27) │ │ │ │ -B9710 Extra Length 0018 (24) │ │ │ │ -B9712 Comment Length 0000 (0) │ │ │ │ -B9714 Disk Start 0000 (0) │ │ │ │ -B9716 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9718 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B971C Local Header Offset 00035A6D (219757) │ │ │ │ -B9720 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9720: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B973B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B973D Length 0005 (5) │ │ │ │ -B973F Flags 01 (1) 'Modification' │ │ │ │ -B9740 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9744 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9746 Length 000B (11) │ │ │ │ -B9748 Version 01 (1) │ │ │ │ -B9749 UID Size 04 (4) │ │ │ │ -B974A UID 00000000 (0) │ │ │ │ -B974E GID Size 04 (4) │ │ │ │ -B974F GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9753 CENTRAL HEADER #27 02014B50 (33639248) │ │ │ │ -B9757 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9758 Created OS 03 (3) 'Unix' │ │ │ │ -B9759 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B975A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B975B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B975D Compression Method 0008 (8) 'Deflated' │ │ │ │ -B975F Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9763 CRC D6E9E108 (3605651720) │ │ │ │ -B9767 Compressed Size 00001371 (4977) │ │ │ │ -B976B Uncompressed Size 00003B61 (15201) │ │ │ │ -B976F Filename Length 0015 (21) │ │ │ │ -B9771 Extra Length 0018 (24) │ │ │ │ -B9773 Comment Length 0000 (0) │ │ │ │ -B9775 Disk Start 0000 (0) │ │ │ │ -B9777 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9779 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B977D Local Header Offset 0003726C (225900) │ │ │ │ -B9781 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9781: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9796 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9798 Length 0005 (5) │ │ │ │ -B979A Flags 01 (1) 'Modification' │ │ │ │ -B979B Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B979F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B97A1 Length 000B (11) │ │ │ │ -B97A3 Version 01 (1) │ │ │ │ -B97A4 UID Size 04 (4) │ │ │ │ -B97A5 UID 00000000 (0) │ │ │ │ -B97A9 GID Size 04 (4) │ │ │ │ -B97AA GID 00000000 (0) │ │ │ │ - │ │ │ │ -B97AE CENTRAL HEADER #28 02014B50 (33639248) │ │ │ │ -B97B2 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B97B3 Created OS 03 (3) 'Unix' │ │ │ │ -B97B4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B97B5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B97B6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B97B8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B97BA Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B97BE CRC AD15BDB2 (2903883186) │ │ │ │ -B97C2 Compressed Size 00000AD0 (2768) │ │ │ │ -B97C6 Uncompressed Size 00002135 (8501) │ │ │ │ -B97CA Filename Length 0011 (17) │ │ │ │ -B97CC Extra Length 0018 (24) │ │ │ │ -B97CE Comment Length 0000 (0) │ │ │ │ -B97D0 Disk Start 0000 (0) │ │ │ │ -B97D2 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B97D4 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B97D8 Local Header Offset 0003862C (230956) │ │ │ │ -B97DC Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB97DC: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B97ED Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B97EF Length 0005 (5) │ │ │ │ -B97F1 Flags 01 (1) 'Modification' │ │ │ │ -B97F2 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B97F6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B97F8 Length 000B (11) │ │ │ │ -B97FA Version 01 (1) │ │ │ │ -B97FB UID Size 04 (4) │ │ │ │ -B97FC UID 00000000 (0) │ │ │ │ -B9800 GID Size 04 (4) │ │ │ │ -B9801 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9805 CENTRAL HEADER #29 02014B50 (33639248) │ │ │ │ -B9809 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B980A Created OS 03 (3) 'Unix' │ │ │ │ -B980B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B980C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B980D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B980F Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9811 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9815 CRC 47BA89D2 (1203407314) │ │ │ │ -B9819 Compressed Size 000003FE (1022) │ │ │ │ -B981D Uncompressed Size 00000F0C (3852) │ │ │ │ -B9821 Filename Length 0014 (20) │ │ │ │ -B9823 Extra Length 0018 (24) │ │ │ │ -B9825 Comment Length 0000 (0) │ │ │ │ -B9827 Disk Start 0000 (0) │ │ │ │ -B9829 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B982B Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B982F Local Header Offset 00039147 (233799) │ │ │ │ -B9833 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9833: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9847 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9849 Length 0005 (5) │ │ │ │ -B984B Flags 01 (1) 'Modification' │ │ │ │ -B984C Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9850 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9852 Length 000B (11) │ │ │ │ -B9854 Version 01 (1) │ │ │ │ -B9855 UID Size 04 (4) │ │ │ │ -B9856 UID 00000000 (0) │ │ │ │ -B985A GID Size 04 (4) │ │ │ │ -B985B GID 00000000 (0) │ │ │ │ - │ │ │ │ -B985F CENTRAL HEADER #30 02014B50 (33639248) │ │ │ │ -B9863 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9864 Created OS 03 (3) 'Unix' │ │ │ │ -B9865 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9866 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9867 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9869 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B986B Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B986F CRC F6397314 (4130960148) │ │ │ │ -B9873 Compressed Size 00001260 (4704) │ │ │ │ -B9877 Uncompressed Size 0000346B (13419) │ │ │ │ -B987B Filename Length 0014 (20) │ │ │ │ -B987D Extra Length 0018 (24) │ │ │ │ -B987F Comment Length 0000 (0) │ │ │ │ -B9881 Disk Start 0000 (0) │ │ │ │ -B9883 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9885 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9889 Local Header Offset 00039593 (234899) │ │ │ │ -B988D Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB988D: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B98A1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B98A3 Length 0005 (5) │ │ │ │ -B98A5 Flags 01 (1) 'Modification' │ │ │ │ -B98A6 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B98AA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B98AC Length 000B (11) │ │ │ │ -B98AE Version 01 (1) │ │ │ │ -B98AF UID Size 04 (4) │ │ │ │ -B98B0 UID 00000000 (0) │ │ │ │ -B98B4 GID Size 04 (4) │ │ │ │ -B98B5 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B98B9 CENTRAL HEADER #31 02014B50 (33639248) │ │ │ │ -B98BD Created Zip Spec 3D (61) '6.1' │ │ │ │ -B98BE Created OS 03 (3) 'Unix' │ │ │ │ -B98BF Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B98C0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B98C1 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B98C3 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B98C5 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B98C9 CRC FB5774F3 (4216812787) │ │ │ │ -B98CD Compressed Size 00000ACF (2767) │ │ │ │ -B98D1 Uncompressed Size 000022FF (8959) │ │ │ │ -B98D5 Filename Length 001B (27) │ │ │ │ -B98D7 Extra Length 0018 (24) │ │ │ │ -B98D9 Comment Length 0000 (0) │ │ │ │ -B98DB Disk Start 0000 (0) │ │ │ │ -B98DD Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B98DF Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B98E3 Local Header Offset 0003A841 (239681) │ │ │ │ -B98E7 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB98E7: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9902 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9904 Length 0005 (5) │ │ │ │ -B9906 Flags 01 (1) 'Modification' │ │ │ │ -B9907 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B990B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B990D Length 000B (11) │ │ │ │ -B990F Version 01 (1) │ │ │ │ -B9910 UID Size 04 (4) │ │ │ │ -B9911 UID 00000000 (0) │ │ │ │ -B9915 GID Size 04 (4) │ │ │ │ -B9916 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B991A CENTRAL HEADER #32 02014B50 (33639248) │ │ │ │ -B991E Created Zip Spec 3D (61) '6.1' │ │ │ │ -B991F Created OS 03 (3) 'Unix' │ │ │ │ -B9920 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9921 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9922 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9924 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9926 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B992A CRC 013FF3F3 (20968435) │ │ │ │ -B992E Compressed Size 00000C4E (3150) │ │ │ │ -B9932 Uncompressed Size 0000273C (10044) │ │ │ │ -B9936 Filename Length 0013 (19) │ │ │ │ -B9938 Extra Length 0018 (24) │ │ │ │ -B993A Comment Length 0000 (0) │ │ │ │ -B993C Disk Start 0000 (0) │ │ │ │ -B993E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9940 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9944 Local Header Offset 0003B365 (242533) │ │ │ │ -B9948 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9948: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B995B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B995D Length 0005 (5) │ │ │ │ -B995F Flags 01 (1) 'Modification' │ │ │ │ -B9960 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9964 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9966 Length 000B (11) │ │ │ │ -B9968 Version 01 (1) │ │ │ │ -B9969 UID Size 04 (4) │ │ │ │ -B996A UID 00000000 (0) │ │ │ │ -B996E GID Size 04 (4) │ │ │ │ -B996F GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9973 CENTRAL HEADER #33 02014B50 (33639248) │ │ │ │ -B9977 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9978 Created OS 03 (3) 'Unix' │ │ │ │ -B9979 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B997A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B997B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B997D Compression Method 0008 (8) 'Deflated' │ │ │ │ -B997F Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9983 CRC 0C6D74B6 (208499894) │ │ │ │ -B9987 Compressed Size 00000C94 (3220) │ │ │ │ -B998B Uncompressed Size 00003D10 (15632) │ │ │ │ -B998F Filename Length 0014 (20) │ │ │ │ -B9991 Extra Length 0018 (24) │ │ │ │ -B9993 Comment Length 0000 (0) │ │ │ │ -B9995 Disk Start 0000 (0) │ │ │ │ -B9997 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9999 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B999D Local Header Offset 0003C000 (245760) │ │ │ │ -B99A1 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB99A1: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B99B5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B99B7 Length 0005 (5) │ │ │ │ -B99B9 Flags 01 (1) 'Modification' │ │ │ │ -B99BA Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B99BE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B99C0 Length 000B (11) │ │ │ │ -B99C2 Version 01 (1) │ │ │ │ -B99C3 UID Size 04 (4) │ │ │ │ -B99C4 UID 00000000 (0) │ │ │ │ -B99C8 GID Size 04 (4) │ │ │ │ -B99C9 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B99CD CENTRAL HEADER #34 02014B50 (33639248) │ │ │ │ -B99D1 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B99D2 Created OS 03 (3) 'Unix' │ │ │ │ -B99D3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B99D4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B99D5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B99D7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B99D9 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B99DD CRC C090FFF6 (3230728182) │ │ │ │ -B99E1 Compressed Size 00000F41 (3905) │ │ │ │ -B99E5 Uncompressed Size 00003744 (14148) │ │ │ │ -B99E9 Filename Length 000F (15) │ │ │ │ -B99EB Extra Length 0018 (24) │ │ │ │ -B99ED Comment Length 0000 (0) │ │ │ │ -B99EF Disk Start 0000 (0) │ │ │ │ -B99F1 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B99F3 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B99F7 Local Header Offset 0003CCE2 (249058) │ │ │ │ -B99FB Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB99FB: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9A0A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9A0C Length 0005 (5) │ │ │ │ -B9A0E Flags 01 (1) 'Modification' │ │ │ │ -B9A0F Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9A13 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9A15 Length 000B (11) │ │ │ │ -B9A17 Version 01 (1) │ │ │ │ -B9A18 UID Size 04 (4) │ │ │ │ -B9A19 UID 00000000 (0) │ │ │ │ -B9A1D GID Size 04 (4) │ │ │ │ -B9A1E GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9A22 CENTRAL HEADER #35 02014B50 (33639248) │ │ │ │ -B9A26 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9A27 Created OS 03 (3) 'Unix' │ │ │ │ -B9A28 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9A29 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9A2A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9A2C Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9A2E Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9A32 CRC 1FC86902 (533227778) │ │ │ │ -B9A36 Compressed Size 000006BA (1722) │ │ │ │ -B9A3A Uncompressed Size 00001A79 (6777) │ │ │ │ -B9A3E Filename Length 000F (15) │ │ │ │ -B9A40 Extra Length 0018 (24) │ │ │ │ -B9A42 Comment Length 0000 (0) │ │ │ │ -B9A44 Disk Start 0000 (0) │ │ │ │ -B9A46 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9A48 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9A4C Local Header Offset 0003DC6C (253036) │ │ │ │ -B9A50 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9A50: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9A5F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9A61 Length 0005 (5) │ │ │ │ -B9A63 Flags 01 (1) 'Modification' │ │ │ │ -B9A64 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9A68 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9A6A Length 000B (11) │ │ │ │ -B9A6C Version 01 (1) │ │ │ │ -B9A6D UID Size 04 (4) │ │ │ │ -B9A6E UID 00000000 (0) │ │ │ │ -B9A72 GID Size 04 (4) │ │ │ │ -B9A73 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9A77 CENTRAL HEADER #36 02014B50 (33639248) │ │ │ │ -B9A7B Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9A7C Created OS 03 (3) 'Unix' │ │ │ │ -B9A7D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9A7E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9A7F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9A81 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9A83 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9A87 CRC 6655DB7E (1716902782) │ │ │ │ -B9A8B Compressed Size 00001A43 (6723) │ │ │ │ -B9A8F Uncompressed Size 000064FA (25850) │ │ │ │ -B9A93 Filename Length 0013 (19) │ │ │ │ -B9A95 Extra Length 0018 (24) │ │ │ │ -B9A97 Comment Length 0000 (0) │ │ │ │ -B9A99 Disk Start 0000 (0) │ │ │ │ -B9A9B Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9A9D Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9AA1 Local Header Offset 0003E36F (254831) │ │ │ │ -B9AA5 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9AA5: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9AB8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9ABA Length 0005 (5) │ │ │ │ -B9ABC Flags 01 (1) 'Modification' │ │ │ │ -B9ABD Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9AC1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9AC3 Length 000B (11) │ │ │ │ -B9AC5 Version 01 (1) │ │ │ │ -B9AC6 UID Size 04 (4) │ │ │ │ -B9AC7 UID 00000000 (0) │ │ │ │ -B9ACB GID Size 04 (4) │ │ │ │ -B9ACC GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9AD0 CENTRAL HEADER #37 02014B50 (33639248) │ │ │ │ -B9AD4 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9AD5 Created OS 03 (3) 'Unix' │ │ │ │ -B9AD6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9AD7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9AD8 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9ADA Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9ADC Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9AE0 CRC 8BADFA46 (2343434822) │ │ │ │ -B9AE4 Compressed Size 000009A5 (2469) │ │ │ │ -B9AE8 Uncompressed Size 00001B64 (7012) │ │ │ │ -B9AEC Filename Length 0010 (16) │ │ │ │ -B9AEE Extra Length 0018 (24) │ │ │ │ -B9AF0 Comment Length 0000 (0) │ │ │ │ -B9AF2 Disk Start 0000 (0) │ │ │ │ -B9AF4 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9AF6 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9AFA Local Header Offset 0003FDFF (261631) │ │ │ │ -B9AFE Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9AFE: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9B0E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9B10 Length 0005 (5) │ │ │ │ -B9B12 Flags 01 (1) 'Modification' │ │ │ │ -B9B13 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9B17 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9B19 Length 000B (11) │ │ │ │ -B9B1B Version 01 (1) │ │ │ │ -B9B1C UID Size 04 (4) │ │ │ │ -B9B1D UID 00000000 (0) │ │ │ │ -B9B21 GID Size 04 (4) │ │ │ │ -B9B22 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9B26 CENTRAL HEADER #38 02014B50 (33639248) │ │ │ │ -B9B2A Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9B2B Created OS 03 (3) 'Unix' │ │ │ │ -B9B2C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9B2D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9B2E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9B30 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9B32 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9B36 CRC 5E895E09 (1586060809) │ │ │ │ -B9B3A Compressed Size 000006B6 (1718) │ │ │ │ -B9B3E Uncompressed Size 00001565 (5477) │ │ │ │ -B9B42 Filename Length 0012 (18) │ │ │ │ -B9B44 Extra Length 0018 (24) │ │ │ │ -B9B46 Comment Length 0000 (0) │ │ │ │ -B9B48 Disk Start 0000 (0) │ │ │ │ -B9B4A Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9B4C Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9B50 Local Header Offset 000407EE (264174) │ │ │ │ -B9B54 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9B54: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9B66 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9B68 Length 0005 (5) │ │ │ │ -B9B6A Flags 01 (1) 'Modification' │ │ │ │ -B9B6B Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9B6F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9B71 Length 000B (11) │ │ │ │ -B9B73 Version 01 (1) │ │ │ │ -B9B74 UID Size 04 (4) │ │ │ │ -B9B75 UID 00000000 (0) │ │ │ │ -B9B79 GID Size 04 (4) │ │ │ │ -B9B7A GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9B7E CENTRAL HEADER #39 02014B50 (33639248) │ │ │ │ -B9B82 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9B83 Created OS 03 (3) 'Unix' │ │ │ │ -B9B84 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9B85 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9B86 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9B88 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9B8A Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9B8E CRC 320A199B (839522715) │ │ │ │ -B9B92 Compressed Size 00002D5C (11612) │ │ │ │ -B9B96 Uncompressed Size 0000D07E (53374) │ │ │ │ -B9B9A Filename Length 0010 (16) │ │ │ │ -B9B9C Extra Length 0018 (24) │ │ │ │ -B9B9E Comment Length 0000 (0) │ │ │ │ -B9BA0 Disk Start 0000 (0) │ │ │ │ -B9BA2 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9BA4 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9BA8 Local Header Offset 00040EF0 (265968) │ │ │ │ -B9BAC Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9BAC: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9BBC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9BBE Length 0005 (5) │ │ │ │ -B9BC0 Flags 01 (1) 'Modification' │ │ │ │ -B9BC1 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9BC5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9BC7 Length 000B (11) │ │ │ │ -B9BC9 Version 01 (1) │ │ │ │ -B9BCA UID Size 04 (4) │ │ │ │ -B9BCB UID 00000000 (0) │ │ │ │ -B9BCF GID Size 04 (4) │ │ │ │ -B9BD0 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9BD4 CENTRAL HEADER #40 02014B50 (33639248) │ │ │ │ -B9BD8 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9BD9 Created OS 03 (3) 'Unix' │ │ │ │ -B9BDA Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9BDB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9BDC General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9BDE Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9BE0 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9BE4 CRC 83A14DD8 (2208386520) │ │ │ │ -B9BE8 Compressed Size 00001E83 (7811) │ │ │ │ -B9BEC Uncompressed Size 00009AAA (39594) │ │ │ │ -B9BF0 Filename Length 0012 (18) │ │ │ │ -B9BF2 Extra Length 0018 (24) │ │ │ │ -B9BF4 Comment Length 0000 (0) │ │ │ │ -B9BF6 Disk Start 0000 (0) │ │ │ │ -B9BF8 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9BFA Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9BFE Local Header Offset 00043C96 (277654) │ │ │ │ -B9C02 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9C02: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9C14 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9C16 Length 0005 (5) │ │ │ │ -B9C18 Flags 01 (1) 'Modification' │ │ │ │ -B9C19 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9C1D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9C1F Length 000B (11) │ │ │ │ -B9C21 Version 01 (1) │ │ │ │ -B9C22 UID Size 04 (4) │ │ │ │ -B9C23 UID 00000000 (0) │ │ │ │ -B9C27 GID Size 04 (4) │ │ │ │ -B9C28 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9C2C CENTRAL HEADER #41 02014B50 (33639248) │ │ │ │ -B9C30 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9C31 Created OS 03 (3) 'Unix' │ │ │ │ -B9C32 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9C33 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9C34 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9C36 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9C38 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9C3C CRC E6CB5983 (3872086403) │ │ │ │ -B9C40 Compressed Size 00001478 (5240) │ │ │ │ -B9C44 Uncompressed Size 00007ACF (31439) │ │ │ │ -B9C48 Filename Length 0018 (24) │ │ │ │ -B9C4A Extra Length 0018 (24) │ │ │ │ -B9C4C Comment Length 0000 (0) │ │ │ │ -B9C4E Disk Start 0000 (0) │ │ │ │ -B9C50 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9C52 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9C56 Local Header Offset 00045B65 (285541) │ │ │ │ -B9C5A Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9C5A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9C72 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9C74 Length 0005 (5) │ │ │ │ -B9C76 Flags 01 (1) 'Modification' │ │ │ │ -B9C77 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9C7B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9C7D Length 000B (11) │ │ │ │ -B9C7F Version 01 (1) │ │ │ │ -B9C80 UID Size 04 (4) │ │ │ │ -B9C81 UID 00000000 (0) │ │ │ │ -B9C85 GID Size 04 (4) │ │ │ │ -B9C86 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9C8A CENTRAL HEADER #42 02014B50 (33639248) │ │ │ │ -B9C8E Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9C8F Created OS 03 (3) 'Unix' │ │ │ │ -B9C90 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9C91 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9C92 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9C94 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9C96 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9C9A CRC 17D9A72D (400140077) │ │ │ │ -B9C9E Compressed Size 000021E0 (8672) │ │ │ │ -B9CA2 Uncompressed Size 0000D220 (53792) │ │ │ │ -B9CA6 Filename Length 001F (31) │ │ │ │ -B9CA8 Extra Length 0018 (24) │ │ │ │ -B9CAA Comment Length 0000 (0) │ │ │ │ -B9CAC Disk Start 0000 (0) │ │ │ │ -B9CAE Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9CB0 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9CB4 Local Header Offset 0004702F (290863) │ │ │ │ -B9CB8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9CB8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9CD7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9CD9 Length 0005 (5) │ │ │ │ -B9CDB Flags 01 (1) 'Modification' │ │ │ │ -B9CDC Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9CE0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9CE2 Length 000B (11) │ │ │ │ -B9CE4 Version 01 (1) │ │ │ │ -B9CE5 UID Size 04 (4) │ │ │ │ -B9CE6 UID 00000000 (0) │ │ │ │ -B9CEA GID Size 04 (4) │ │ │ │ -B9CEB GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9CEF CENTRAL HEADER #43 02014B50 (33639248) │ │ │ │ -B9CF3 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9CF4 Created OS 03 (3) 'Unix' │ │ │ │ -B9CF5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9CF6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9CF7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9CF9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9CFB Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9CFF CRC E6EB8E0C (3874197004) │ │ │ │ -B9D03 Compressed Size 000003F7 (1015) │ │ │ │ -B9D07 Uncompressed Size 000008A3 (2211) │ │ │ │ -B9D0B Filename Length 001E (30) │ │ │ │ -B9D0D Extra Length 0018 (24) │ │ │ │ -B9D0F Comment Length 0000 (0) │ │ │ │ -B9D11 Disk Start 0000 (0) │ │ │ │ -B9D13 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9D15 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9D19 Local Header Offset 00049268 (299624) │ │ │ │ -B9D1D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9D1D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9D3B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9D3D Length 0005 (5) │ │ │ │ -B9D3F Flags 01 (1) 'Modification' │ │ │ │ -B9D40 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9D44 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9D46 Length 000B (11) │ │ │ │ -B9D48 Version 01 (1) │ │ │ │ -B9D49 UID Size 04 (4) │ │ │ │ -B9D4A UID 00000000 (0) │ │ │ │ -B9D4E GID Size 04 (4) │ │ │ │ -B9D4F GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9D53 CENTRAL HEADER #44 02014B50 (33639248) │ │ │ │ -B9D57 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9D58 Created OS 03 (3) 'Unix' │ │ │ │ -B9D59 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9D5A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9D5B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9D5D Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9D5F Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9D63 CRC 352C2657 (892085847) │ │ │ │ -B9D67 Compressed Size 000042FE (17150) │ │ │ │ -B9D6B Uncompressed Size 0000DAE1 (56033) │ │ │ │ -B9D6F Filename Length 0013 (19) │ │ │ │ -B9D71 Extra Length 0018 (24) │ │ │ │ -B9D73 Comment Length 0000 (0) │ │ │ │ -B9D75 Disk Start 0000 (0) │ │ │ │ -B9D77 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9D79 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9D7D Local Header Offset 000496B7 (300727) │ │ │ │ -B9D81 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9D81: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9D94 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9D96 Length 0005 (5) │ │ │ │ -B9D98 Flags 01 (1) 'Modification' │ │ │ │ -B9D99 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9D9D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9D9F Length 000B (11) │ │ │ │ -B9DA1 Version 01 (1) │ │ │ │ -B9DA2 UID Size 04 (4) │ │ │ │ -B9DA3 UID 00000000 (0) │ │ │ │ -B9DA7 GID Size 04 (4) │ │ │ │ -B9DA8 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9DAC CENTRAL HEADER #45 02014B50 (33639248) │ │ │ │ -B9DB0 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9DB1 Created OS 03 (3) 'Unix' │ │ │ │ -B9DB2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9DB3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9DB4 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9DB6 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9DB8 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9DBC CRC 6505DF0C (1694883596) │ │ │ │ -B9DC0 Compressed Size 000026C1 (9921) │ │ │ │ -B9DC4 Uncompressed Size 00006E45 (28229) │ │ │ │ -B9DC8 Filename Length 0019 (25) │ │ │ │ -B9DCA Extra Length 0018 (24) │ │ │ │ -B9DCC Comment Length 0000 (0) │ │ │ │ -B9DCE Disk Start 0000 (0) │ │ │ │ -B9DD0 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9DD2 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9DD6 Local Header Offset 0004DA02 (317954) │ │ │ │ -B9DDA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9DDA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9DF3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9DF5 Length 0005 (5) │ │ │ │ -B9DF7 Flags 01 (1) 'Modification' │ │ │ │ -B9DF8 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9DFC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9DFE Length 000B (11) │ │ │ │ -B9E00 Version 01 (1) │ │ │ │ -B9E01 UID Size 04 (4) │ │ │ │ -B9E02 UID 00000000 (0) │ │ │ │ -B9E06 GID Size 04 (4) │ │ │ │ -B9E07 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9E0B CENTRAL HEADER #46 02014B50 (33639248) │ │ │ │ -B9E0F Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9E10 Created OS 03 (3) 'Unix' │ │ │ │ -B9E11 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9E12 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9E13 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9E15 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9E17 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9E1B CRC BCB71496 (3166114966) │ │ │ │ -B9E1F Compressed Size 00002739 (10041) │ │ │ │ -B9E23 Uncompressed Size 00008B83 (35715) │ │ │ │ -B9E27 Filename Length 0019 (25) │ │ │ │ -B9E29 Extra Length 0018 (24) │ │ │ │ -B9E2B Comment Length 0000 (0) │ │ │ │ -B9E2D Disk Start 0000 (0) │ │ │ │ -B9E2F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9E31 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9E35 Local Header Offset 00050116 (327958) │ │ │ │ -B9E39 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9E39: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9E52 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9E54 Length 0005 (5) │ │ │ │ -B9E56 Flags 01 (1) 'Modification' │ │ │ │ -B9E57 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9E5B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9E5D Length 000B (11) │ │ │ │ -B9E5F Version 01 (1) │ │ │ │ -B9E60 UID Size 04 (4) │ │ │ │ -B9E61 UID 00000000 (0) │ │ │ │ -B9E65 GID Size 04 (4) │ │ │ │ -B9E66 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9E6A CENTRAL HEADER #47 02014B50 (33639248) │ │ │ │ -B9E6E Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9E6F Created OS 03 (3) 'Unix' │ │ │ │ -B9E70 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9E71 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9E72 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9E74 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9E76 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9E7A CRC 2F6969CF (795437519) │ │ │ │ -B9E7E Compressed Size 00000ECD (3789) │ │ │ │ -B9E82 Uncompressed Size 000053BF (21439) │ │ │ │ -B9E86 Filename Length 0021 (33) │ │ │ │ -B9E88 Extra Length 0018 (24) │ │ │ │ -B9E8A Comment Length 0000 (0) │ │ │ │ -B9E8C Disk Start 0000 (0) │ │ │ │ -B9E8E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9E90 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9E94 Local Header Offset 000528A2 (338082) │ │ │ │ -B9E98 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9E98: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9EB9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9EBB Length 0005 (5) │ │ │ │ -B9EBD Flags 01 (1) 'Modification' │ │ │ │ -B9EBE Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9EC2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9EC4 Length 000B (11) │ │ │ │ -B9EC6 Version 01 (1) │ │ │ │ -B9EC7 UID Size 04 (4) │ │ │ │ -B9EC8 UID 00000000 (0) │ │ │ │ -B9ECC GID Size 04 (4) │ │ │ │ -B9ECD GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9ED1 CENTRAL HEADER #48 02014B50 (33639248) │ │ │ │ -B9ED5 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9ED6 Created OS 03 (3) 'Unix' │ │ │ │ -B9ED7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9ED8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9ED9 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9EDB Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9EDD Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9EE1 CRC 405AA2A7 (1079681703) │ │ │ │ -B9EE5 Compressed Size 00000535 (1333) │ │ │ │ -B9EE9 Uncompressed Size 00000C96 (3222) │ │ │ │ -B9EED Filename Length 0017 (23) │ │ │ │ -B9EEF Extra Length 0018 (24) │ │ │ │ -B9EF1 Comment Length 0000 (0) │ │ │ │ -B9EF3 Disk Start 0000 (0) │ │ │ │ -B9EF5 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9EF7 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9EFB Local Header Offset 000537CA (341962) │ │ │ │ -B9EFF Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9EFF: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9F16 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9F18 Length 0005 (5) │ │ │ │ -B9F1A Flags 01 (1) 'Modification' │ │ │ │ -B9F1B Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9F1F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9F21 Length 000B (11) │ │ │ │ -B9F23 Version 01 (1) │ │ │ │ -B9F24 UID Size 04 (4) │ │ │ │ -B9F25 UID 00000000 (0) │ │ │ │ -B9F29 GID Size 04 (4) │ │ │ │ -B9F2A GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9F2E CENTRAL HEADER #49 02014B50 (33639248) │ │ │ │ -B9F32 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9F33 Created OS 03 (3) 'Unix' │ │ │ │ -B9F34 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9F35 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9F36 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9F38 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9F3A Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9F3E CRC DB679B08 (3681000200) │ │ │ │ -B9F42 Compressed Size 00000467 (1127) │ │ │ │ -B9F46 Uncompressed Size 00000931 (2353) │ │ │ │ -B9F4A Filename Length 001B (27) │ │ │ │ -B9F4C Extra Length 0018 (24) │ │ │ │ -B9F4E Comment Length 0000 (0) │ │ │ │ -B9F50 Disk Start 0000 (0) │ │ │ │ -B9F52 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9F54 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9F58 Local Header Offset 00053D50 (343376) │ │ │ │ -B9F5C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9F5C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9F77 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9F79 Length 0005 (5) │ │ │ │ -B9F7B Flags 01 (1) 'Modification' │ │ │ │ -B9F7C Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9F80 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9F82 Length 000B (11) │ │ │ │ -B9F84 Version 01 (1) │ │ │ │ -B9F85 UID Size 04 (4) │ │ │ │ -B9F86 UID 00000000 (0) │ │ │ │ -B9F8A GID Size 04 (4) │ │ │ │ -B9F8B GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9F8F CENTRAL HEADER #50 02014B50 (33639248) │ │ │ │ -B9F93 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9F94 Created OS 03 (3) 'Unix' │ │ │ │ -B9F95 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9F96 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9F97 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9F99 Compression Method 0008 (8) 'Deflated' │ │ │ │ -B9F9B Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -B9F9F CRC 30DFE74E (819980110) │ │ │ │ -B9FA3 Compressed Size 000016EE (5870) │ │ │ │ -B9FA7 Uncompressed Size 00007A6D (31341) │ │ │ │ -B9FAB Filename Length 001F (31) │ │ │ │ -B9FAD Extra Length 0018 (24) │ │ │ │ -B9FAF Comment Length 0000 (0) │ │ │ │ -B9FB1 Disk Start 0000 (0) │ │ │ │ -B9FB3 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -B9FB5 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -B9FB9 Local Header Offset 0005420C (344588) │ │ │ │ -B9FBD Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xB9FBD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -B9FDC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -B9FDE Length 0005 (5) │ │ │ │ -B9FE0 Flags 01 (1) 'Modification' │ │ │ │ -B9FE1 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -B9FE5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -B9FE7 Length 000B (11) │ │ │ │ -B9FE9 Version 01 (1) │ │ │ │ -B9FEA UID Size 04 (4) │ │ │ │ -B9FEB UID 00000000 (0) │ │ │ │ -B9FEF GID Size 04 (4) │ │ │ │ -B9FF0 GID 00000000 (0) │ │ │ │ - │ │ │ │ -B9FF4 CENTRAL HEADER #51 02014B50 (33639248) │ │ │ │ -B9FF8 Created Zip Spec 3D (61) '6.1' │ │ │ │ -B9FF9 Created OS 03 (3) 'Unix' │ │ │ │ -B9FFA Extract Zip Spec 14 (20) '2.0' │ │ │ │ -B9FFB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -B9FFC General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -B9FFE Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA000 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA004 CRC 30985937 (815290679) │ │ │ │ -BA008 Compressed Size 00004163 (16739) │ │ │ │ -BA00C Uncompressed Size 0001D15F (119135) │ │ │ │ -BA010 Filename Length 0010 (16) │ │ │ │ -BA012 Extra Length 0018 (24) │ │ │ │ -BA014 Comment Length 0000 (0) │ │ │ │ -BA016 Disk Start 0000 (0) │ │ │ │ -BA018 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA01A Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA01E Local Header Offset 00055953 (350547) │ │ │ │ -BA022 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA022: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA032 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA034 Length 0005 (5) │ │ │ │ -BA036 Flags 01 (1) 'Modification' │ │ │ │ -BA037 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA03B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA03D Length 000B (11) │ │ │ │ -BA03F Version 01 (1) │ │ │ │ -BA040 UID Size 04 (4) │ │ │ │ -BA041 UID 00000000 (0) │ │ │ │ -BA045 GID Size 04 (4) │ │ │ │ -BA046 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA04A CENTRAL HEADER #52 02014B50 (33639248) │ │ │ │ -BA04E Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA04F Created OS 03 (3) 'Unix' │ │ │ │ -BA050 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA051 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA052 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA054 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA056 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA05A CRC E65F34EA (3864999146) │ │ │ │ -BA05E Compressed Size 00000AE6 (2790) │ │ │ │ -BA062 Uncompressed Size 00002229 (8745) │ │ │ │ -BA066 Filename Length 0014 (20) │ │ │ │ -BA068 Extra Length 0018 (24) │ │ │ │ -BA06A Comment Length 0000 (0) │ │ │ │ -BA06C Disk Start 0000 (0) │ │ │ │ -BA06E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA070 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA074 Local Header Offset 00059B00 (367360) │ │ │ │ -BA078 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA078: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA08C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA08E Length 0005 (5) │ │ │ │ -BA090 Flags 01 (1) 'Modification' │ │ │ │ -BA091 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA095 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA097 Length 000B (11) │ │ │ │ -BA099 Version 01 (1) │ │ │ │ -BA09A UID Size 04 (4) │ │ │ │ -BA09B UID 00000000 (0) │ │ │ │ -BA09F GID Size 04 (4) │ │ │ │ -BA0A0 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA0A4 CENTRAL HEADER #53 02014B50 (33639248) │ │ │ │ -BA0A8 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA0A9 Created OS 03 (3) 'Unix' │ │ │ │ -BA0AA Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA0AB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA0AC General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA0AE Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA0B0 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA0B4 CRC 3129212D (824779053) │ │ │ │ -BA0B8 Compressed Size 0000B54D (46413) │ │ │ │ -BA0BC Uncompressed Size 000418FB (268539) │ │ │ │ -BA0C0 Filename Length 0017 (23) │ │ │ │ -BA0C2 Extra Length 0018 (24) │ │ │ │ -BA0C4 Comment Length 0000 (0) │ │ │ │ -BA0C6 Disk Start 0000 (0) │ │ │ │ -BA0C8 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA0CA Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA0CE Local Header Offset 0005A634 (370228) │ │ │ │ -BA0D2 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA0D2: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA0E9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA0EB Length 0005 (5) │ │ │ │ -BA0ED Flags 01 (1) 'Modification' │ │ │ │ -BA0EE Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA0F2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA0F4 Length 000B (11) │ │ │ │ -BA0F6 Version 01 (1) │ │ │ │ -BA0F7 UID Size 04 (4) │ │ │ │ -BA0F8 UID 00000000 (0) │ │ │ │ -BA0FC GID Size 04 (4) │ │ │ │ -BA0FD GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA101 CENTRAL HEADER #54 02014B50 (33639248) │ │ │ │ -BA105 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA106 Created OS 03 (3) 'Unix' │ │ │ │ -BA107 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA108 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA109 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA10B Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA10D Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA111 CRC 29267376 (690385782) │ │ │ │ -BA115 Compressed Size 00000400 (1024) │ │ │ │ -BA119 Uncompressed Size 0000093D (2365) │ │ │ │ -BA11D Filename Length 0013 (19) │ │ │ │ -BA11F Extra Length 0018 (24) │ │ │ │ -BA121 Comment Length 0000 (0) │ │ │ │ -BA123 Disk Start 0000 (0) │ │ │ │ -BA125 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA127 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA12B Local Header Offset 00065BD2 (416722) │ │ │ │ -BA12F Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA12F: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA142 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA144 Length 0005 (5) │ │ │ │ -BA146 Flags 01 (1) 'Modification' │ │ │ │ -BA147 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA14B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA14D Length 000B (11) │ │ │ │ -BA14F Version 01 (1) │ │ │ │ -BA150 UID Size 04 (4) │ │ │ │ -BA151 UID 00000000 (0) │ │ │ │ -BA155 GID Size 04 (4) │ │ │ │ -BA156 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA15A CENTRAL HEADER #55 02014B50 (33639248) │ │ │ │ -BA15E Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA15F Created OS 03 (3) 'Unix' │ │ │ │ -BA160 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA161 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA162 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA164 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA166 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA16A CRC EEDA96E8 (4007302888) │ │ │ │ -BA16E Compressed Size 000014D7 (5335) │ │ │ │ -BA172 Uncompressed Size 00006892 (26770) │ │ │ │ -BA176 Filename Length 0012 (18) │ │ │ │ -BA178 Extra Length 0018 (24) │ │ │ │ -BA17A Comment Length 0000 (0) │ │ │ │ -BA17C Disk Start 0000 (0) │ │ │ │ -BA17E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA180 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA184 Local Header Offset 0006601F (417823) │ │ │ │ -BA188 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA188: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA19A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA19C Length 0005 (5) │ │ │ │ -BA19E Flags 01 (1) 'Modification' │ │ │ │ -BA19F Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA1A3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA1A5 Length 000B (11) │ │ │ │ -BA1A7 Version 01 (1) │ │ │ │ -BA1A8 UID Size 04 (4) │ │ │ │ -BA1A9 UID 00000000 (0) │ │ │ │ -BA1AD GID Size 04 (4) │ │ │ │ -BA1AE GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA1B2 CENTRAL HEADER #56 02014B50 (33639248) │ │ │ │ -BA1B6 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA1B7 Created OS 03 (3) 'Unix' │ │ │ │ -BA1B8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA1B9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA1BA General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA1BC Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA1BE Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA1C2 CRC F3842A14 (4085525012) │ │ │ │ -BA1C6 Compressed Size 000011EA (4586) │ │ │ │ -BA1CA Uncompressed Size 000040FA (16634) │ │ │ │ -BA1CE Filename Length 0012 (18) │ │ │ │ -BA1D0 Extra Length 0018 (24) │ │ │ │ -BA1D2 Comment Length 0000 (0) │ │ │ │ -BA1D4 Disk Start 0000 (0) │ │ │ │ -BA1D6 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA1D8 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA1DC Local Header Offset 00067542 (423234) │ │ │ │ -BA1E0 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA1E0: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA1F2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA1F4 Length 0005 (5) │ │ │ │ -BA1F6 Flags 01 (1) 'Modification' │ │ │ │ -BA1F7 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA1FB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA1FD Length 000B (11) │ │ │ │ -BA1FF Version 01 (1) │ │ │ │ -BA200 UID Size 04 (4) │ │ │ │ -BA201 UID 00000000 (0) │ │ │ │ -BA205 GID Size 04 (4) │ │ │ │ -BA206 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA20A CENTRAL HEADER #57 02014B50 (33639248) │ │ │ │ -BA20E Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA20F Created OS 03 (3) 'Unix' │ │ │ │ -BA210 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA211 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA212 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA214 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA216 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA21A CRC A9CAB3BC (2848633788) │ │ │ │ -BA21E Compressed Size 000009DA (2522) │ │ │ │ -BA222 Uncompressed Size 00003529 (13609) │ │ │ │ -BA226 Filename Length 0019 (25) │ │ │ │ -BA228 Extra Length 0018 (24) │ │ │ │ -BA22A Comment Length 0000 (0) │ │ │ │ -BA22C Disk Start 0000 (0) │ │ │ │ -BA22E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA230 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA234 Local Header Offset 00068778 (427896) │ │ │ │ -BA238 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA238: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA251 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA253 Length 0005 (5) │ │ │ │ -BA255 Flags 01 (1) 'Modification' │ │ │ │ -BA256 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA25A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA25C Length 000B (11) │ │ │ │ -BA25E Version 01 (1) │ │ │ │ -BA25F UID Size 04 (4) │ │ │ │ -BA260 UID 00000000 (0) │ │ │ │ -BA264 GID Size 04 (4) │ │ │ │ -BA265 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA269 CENTRAL HEADER #58 02014B50 (33639248) │ │ │ │ -BA26D Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA26E Created OS 03 (3) 'Unix' │ │ │ │ -BA26F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA270 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA271 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA273 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA275 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA279 CRC 6D5A95BB (1834653115) │ │ │ │ -BA27D Compressed Size 000018B8 (6328) │ │ │ │ -BA281 Uncompressed Size 0000A678 (42616) │ │ │ │ -BA285 Filename Length 0019 (25) │ │ │ │ -BA287 Extra Length 0018 (24) │ │ │ │ -BA289 Comment Length 0000 (0) │ │ │ │ -BA28B Disk Start 0000 (0) │ │ │ │ -BA28D Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA28F Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA293 Local Header Offset 000691A5 (430501) │ │ │ │ -BA297 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA297: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA2B0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA2B2 Length 0005 (5) │ │ │ │ -BA2B4 Flags 01 (1) 'Modification' │ │ │ │ -BA2B5 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA2B9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA2BB Length 000B (11) │ │ │ │ -BA2BD Version 01 (1) │ │ │ │ -BA2BE UID Size 04 (4) │ │ │ │ -BA2BF UID 00000000 (0) │ │ │ │ -BA2C3 GID Size 04 (4) │ │ │ │ -BA2C4 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA2C8 CENTRAL HEADER #59 02014B50 (33639248) │ │ │ │ -BA2CC Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA2CD Created OS 03 (3) 'Unix' │ │ │ │ -BA2CE Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA2CF Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA2D0 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA2D2 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA2D4 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA2D8 CRC EB9538F3 (3952425203) │ │ │ │ -BA2DC Compressed Size 0000177C (6012) │ │ │ │ -BA2E0 Uncompressed Size 0000472C (18220) │ │ │ │ -BA2E4 Filename Length 0014 (20) │ │ │ │ -BA2E6 Extra Length 0018 (24) │ │ │ │ -BA2E8 Comment Length 0000 (0) │ │ │ │ -BA2EA Disk Start 0000 (0) │ │ │ │ -BA2EC Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA2EE Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA2F2 Local Header Offset 0006AAB0 (436912) │ │ │ │ -BA2F6 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA2F6: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA30A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA30C Length 0005 (5) │ │ │ │ -BA30E Flags 01 (1) 'Modification' │ │ │ │ -BA30F Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA313 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA315 Length 000B (11) │ │ │ │ -BA317 Version 01 (1) │ │ │ │ -BA318 UID Size 04 (4) │ │ │ │ -BA319 UID 00000000 (0) │ │ │ │ -BA31D GID Size 04 (4) │ │ │ │ -BA31E GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA322 CENTRAL HEADER #60 02014B50 (33639248) │ │ │ │ -BA326 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA327 Created OS 03 (3) 'Unix' │ │ │ │ -BA328 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA329 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA32A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA32C Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA32E Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA332 CRC F6F034FB (4142937339) │ │ │ │ -BA336 Compressed Size 00000409 (1033) │ │ │ │ -BA33A Uncompressed Size 00000825 (2085) │ │ │ │ -BA33E Filename Length 001C (28) │ │ │ │ -BA340 Extra Length 0018 (24) │ │ │ │ -BA342 Comment Length 0000 (0) │ │ │ │ -BA344 Disk Start 0000 (0) │ │ │ │ -BA346 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA348 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA34C Local Header Offset 0006C27A (443002) │ │ │ │ -BA350 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA350: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA36C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA36E Length 0005 (5) │ │ │ │ -BA370 Flags 01 (1) 'Modification' │ │ │ │ -BA371 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA375 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA377 Length 000B (11) │ │ │ │ -BA379 Version 01 (1) │ │ │ │ -BA37A UID Size 04 (4) │ │ │ │ -BA37B UID 00000000 (0) │ │ │ │ -BA37F GID Size 04 (4) │ │ │ │ -BA380 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA384 CENTRAL HEADER #61 02014B50 (33639248) │ │ │ │ -BA388 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA389 Created OS 03 (3) 'Unix' │ │ │ │ -BA38A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA38B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA38C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA38E Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA390 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA394 CRC 40D420B6 (1087643830) │ │ │ │ -BA398 Compressed Size 000024BB (9403) │ │ │ │ -BA39C Uncompressed Size 0000B65B (46683) │ │ │ │ -BA3A0 Filename Length 001F (31) │ │ │ │ -BA3A2 Extra Length 0018 (24) │ │ │ │ -BA3A4 Comment Length 0000 (0) │ │ │ │ -BA3A6 Disk Start 0000 (0) │ │ │ │ -BA3A8 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA3AA Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA3AE Local Header Offset 0006C6D9 (444121) │ │ │ │ -BA3B2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA3B2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA3D1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA3D3 Length 0005 (5) │ │ │ │ -BA3D5 Flags 01 (1) 'Modification' │ │ │ │ -BA3D6 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA3DA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA3DC Length 000B (11) │ │ │ │ -BA3DE Version 01 (1) │ │ │ │ -BA3DF UID Size 04 (4) │ │ │ │ -BA3E0 UID 00000000 (0) │ │ │ │ -BA3E4 GID Size 04 (4) │ │ │ │ -BA3E5 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA3E9 CENTRAL HEADER #62 02014B50 (33639248) │ │ │ │ -BA3ED Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA3EE Created OS 03 (3) 'Unix' │ │ │ │ -BA3EF Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA3F0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA3F1 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA3F3 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA3F5 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA3F9 CRC A8CFEE19 (2832199193) │ │ │ │ -BA3FD Compressed Size 00000E7E (3710) │ │ │ │ -BA401 Uncompressed Size 000052D9 (21209) │ │ │ │ -BA405 Filename Length 001F (31) │ │ │ │ -BA407 Extra Length 0018 (24) │ │ │ │ -BA409 Comment Length 0000 (0) │ │ │ │ -BA40B Disk Start 0000 (0) │ │ │ │ -BA40D Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA40F Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA413 Local Header Offset 0006EBED (453613) │ │ │ │ -BA417 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA417: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA436 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA438 Length 0005 (5) │ │ │ │ -BA43A Flags 01 (1) 'Modification' │ │ │ │ -BA43B Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA43F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA441 Length 000B (11) │ │ │ │ -BA443 Version 01 (1) │ │ │ │ -BA444 UID Size 04 (4) │ │ │ │ -BA445 UID 00000000 (0) │ │ │ │ -BA449 GID Size 04 (4) │ │ │ │ -BA44A GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA44E CENTRAL HEADER #63 02014B50 (33639248) │ │ │ │ -BA452 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA453 Created OS 03 (3) 'Unix' │ │ │ │ -BA454 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA455 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA456 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA458 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA45A Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA45E CRC D674A862 (3597969506) │ │ │ │ -BA462 Compressed Size 00000A44 (2628) │ │ │ │ -BA466 Uncompressed Size 0000247A (9338) │ │ │ │ -BA46A Filename Length 0013 (19) │ │ │ │ -BA46C Extra Length 0018 (24) │ │ │ │ -BA46E Comment Length 0000 (0) │ │ │ │ -BA470 Disk Start 0000 (0) │ │ │ │ -BA472 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA474 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA478 Local Header Offset 0006FAC4 (457412) │ │ │ │ -BA47C Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA47C: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA48F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA491 Length 0005 (5) │ │ │ │ -BA493 Flags 01 (1) 'Modification' │ │ │ │ -BA494 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA498 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA49A Length 000B (11) │ │ │ │ -BA49C Version 01 (1) │ │ │ │ -BA49D UID Size 04 (4) │ │ │ │ -BA49E UID 00000000 (0) │ │ │ │ -BA4A2 GID Size 04 (4) │ │ │ │ -BA4A3 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA4A7 CENTRAL HEADER #64 02014B50 (33639248) │ │ │ │ -BA4AB Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA4AC Created OS 03 (3) 'Unix' │ │ │ │ -BA4AD Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA4AE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA4AF General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA4B1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA4B3 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA4B7 CRC 6F979343 (1872204611) │ │ │ │ -BA4BB Compressed Size 0000257B (9595) │ │ │ │ -BA4BF Uncompressed Size 0000BA5F (47711) │ │ │ │ -BA4C3 Filename Length 0019 (25) │ │ │ │ -BA4C5 Extra Length 0018 (24) │ │ │ │ -BA4C7 Comment Length 0000 (0) │ │ │ │ -BA4C9 Disk Start 0000 (0) │ │ │ │ -BA4CB Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA4CD Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA4D1 Local Header Offset 00070555 (460117) │ │ │ │ -BA4D5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA4D5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA4EE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA4F0 Length 0005 (5) │ │ │ │ -BA4F2 Flags 01 (1) 'Modification' │ │ │ │ -BA4F3 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA4F7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA4F9 Length 000B (11) │ │ │ │ -BA4FB Version 01 (1) │ │ │ │ -BA4FC UID Size 04 (4) │ │ │ │ -BA4FD UID 00000000 (0) │ │ │ │ -BA501 GID Size 04 (4) │ │ │ │ -BA502 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA506 CENTRAL HEADER #65 02014B50 (33639248) │ │ │ │ -BA50A Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA50B Created OS 03 (3) 'Unix' │ │ │ │ -BA50C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA50D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA50E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA510 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA512 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA516 CRC DD1D8F22 (3709701922) │ │ │ │ -BA51A Compressed Size 00000EF8 (3832) │ │ │ │ -BA51E Uncompressed Size 00003A2C (14892) │ │ │ │ -BA522 Filename Length 0024 (36) │ │ │ │ -BA524 Extra Length 0018 (24) │ │ │ │ -BA526 Comment Length 0000 (0) │ │ │ │ -BA528 Disk Start 0000 (0) │ │ │ │ -BA52A Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA52C Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA530 Local Header Offset 00072B23 (469795) │ │ │ │ -BA534 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA534: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA558 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA55A Length 0005 (5) │ │ │ │ -BA55C Flags 01 (1) 'Modification' │ │ │ │ -BA55D Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA561 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA563 Length 000B (11) │ │ │ │ -BA565 Version 01 (1) │ │ │ │ -BA566 UID Size 04 (4) │ │ │ │ -BA567 UID 00000000 (0) │ │ │ │ -BA56B GID Size 04 (4) │ │ │ │ -BA56C GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA570 CENTRAL HEADER #66 02014B50 (33639248) │ │ │ │ -BA574 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA575 Created OS 03 (3) 'Unix' │ │ │ │ -BA576 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA577 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA578 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA57A Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA57C Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA580 CRC C97A7AA4 (3380247204) │ │ │ │ -BA584 Compressed Size 00001AEB (6891) │ │ │ │ -BA588 Uncompressed Size 00005F82 (24450) │ │ │ │ -BA58C Filename Length 0017 (23) │ │ │ │ -BA58E Extra Length 0018 (24) │ │ │ │ -BA590 Comment Length 0000 (0) │ │ │ │ -BA592 Disk Start 0000 (0) │ │ │ │ -BA594 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA596 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA59A Local Header Offset 00073A79 (473721) │ │ │ │ -BA59E Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA59E: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA5B5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA5B7 Length 0005 (5) │ │ │ │ -BA5B9 Flags 01 (1) 'Modification' │ │ │ │ -BA5BA Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA5BE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA5C0 Length 000B (11) │ │ │ │ -BA5C2 Version 01 (1) │ │ │ │ -BA5C3 UID Size 04 (4) │ │ │ │ -BA5C4 UID 00000000 (0) │ │ │ │ -BA5C8 GID Size 04 (4) │ │ │ │ -BA5C9 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA5CD CENTRAL HEADER #67 02014B50 (33639248) │ │ │ │ -BA5D1 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA5D2 Created OS 03 (3) 'Unix' │ │ │ │ -BA5D3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA5D4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA5D5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA5D7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA5D9 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA5DD CRC 11E32AF1 (300100337) │ │ │ │ -BA5E1 Compressed Size 00000ED3 (3795) │ │ │ │ -BA5E5 Uncompressed Size 000038E2 (14562) │ │ │ │ -BA5E9 Filename Length 0023 (35) │ │ │ │ -BA5EB Extra Length 0018 (24) │ │ │ │ -BA5ED Comment Length 0000 (0) │ │ │ │ -BA5EF Disk Start 0000 (0) │ │ │ │ -BA5F1 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA5F3 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA5F7 Local Header Offset 000755B5 (480693) │ │ │ │ -BA5FB Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA5FB: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA61E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA620 Length 0005 (5) │ │ │ │ -BA622 Flags 01 (1) 'Modification' │ │ │ │ -BA623 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA627 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA629 Length 000B (11) │ │ │ │ -BA62B Version 01 (1) │ │ │ │ -BA62C UID Size 04 (4) │ │ │ │ -BA62D UID 00000000 (0) │ │ │ │ -BA631 GID Size 04 (4) │ │ │ │ -BA632 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA636 CENTRAL HEADER #68 02014B50 (33639248) │ │ │ │ -BA63A Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA63B Created OS 03 (3) 'Unix' │ │ │ │ -BA63C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA63D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA63E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA640 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA642 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA646 CRC 2DB7929F (767005343) │ │ │ │ -BA64A Compressed Size 00000113 (275) │ │ │ │ -BA64E Uncompressed Size 000001F3 (499) │ │ │ │ -BA652 Filename Length 001B (27) │ │ │ │ -BA654 Extra Length 0018 (24) │ │ │ │ -BA656 Comment Length 0000 (0) │ │ │ │ -BA658 Disk Start 0000 (0) │ │ │ │ -BA65A Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA65C Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA660 Local Header Offset 000764E5 (484581) │ │ │ │ -BA664 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA664: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA67F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA681 Length 0005 (5) │ │ │ │ -BA683 Flags 01 (1) 'Modification' │ │ │ │ -BA684 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA688 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA68A Length 000B (11) │ │ │ │ -BA68C Version 01 (1) │ │ │ │ -BA68D UID Size 04 (4) │ │ │ │ -BA68E UID 00000000 (0) │ │ │ │ -BA692 GID Size 04 (4) │ │ │ │ -BA693 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA697 CENTRAL HEADER #69 02014B50 (33639248) │ │ │ │ -BA69B Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA69C Created OS 03 (3) 'Unix' │ │ │ │ -BA69D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA69E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA69F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA6A1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA6A3 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA6A7 CRC 852C941F (2234291231) │ │ │ │ -BA6AB Compressed Size 00001891 (6289) │ │ │ │ -BA6AF Uncompressed Size 00008FAC (36780) │ │ │ │ -BA6B3 Filename Length 001D (29) │ │ │ │ -BA6B5 Extra Length 0018 (24) │ │ │ │ -BA6B7 Comment Length 0000 (0) │ │ │ │ -BA6B9 Disk Start 0000 (0) │ │ │ │ -BA6BB Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA6BD Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA6C1 Local Header Offset 0007664D (484941) │ │ │ │ -BA6C5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA6C5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA6E2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA6E4 Length 0005 (5) │ │ │ │ -BA6E6 Flags 01 (1) 'Modification' │ │ │ │ -BA6E7 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA6EB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA6ED Length 000B (11) │ │ │ │ -BA6EF Version 01 (1) │ │ │ │ -BA6F0 UID Size 04 (4) │ │ │ │ -BA6F1 UID 00000000 (0) │ │ │ │ -BA6F5 GID Size 04 (4) │ │ │ │ -BA6F6 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA6FA CENTRAL HEADER #70 02014B50 (33639248) │ │ │ │ -BA6FE Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA6FF Created OS 03 (3) 'Unix' │ │ │ │ -BA700 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA701 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA702 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA704 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA706 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA70A CRC 905D47D7 (2422032343) │ │ │ │ -BA70E Compressed Size 0000164C (5708) │ │ │ │ -BA712 Uncompressed Size 00003A9B (15003) │ │ │ │ -BA716 Filename Length 0015 (21) │ │ │ │ -BA718 Extra Length 0018 (24) │ │ │ │ -BA71A Comment Length 0000 (0) │ │ │ │ -BA71C Disk Start 0000 (0) │ │ │ │ -BA71E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA720 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA724 Local Header Offset 00077F35 (491317) │ │ │ │ -BA728 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA728: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA73D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA73F Length 0005 (5) │ │ │ │ -BA741 Flags 01 (1) 'Modification' │ │ │ │ -BA742 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA746 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA748 Length 000B (11) │ │ │ │ -BA74A Version 01 (1) │ │ │ │ -BA74B UID Size 04 (4) │ │ │ │ -BA74C UID 00000000 (0) │ │ │ │ -BA750 GID Size 04 (4) │ │ │ │ -BA751 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA755 CENTRAL HEADER #71 02014B50 (33639248) │ │ │ │ -BA759 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA75A Created OS 03 (3) 'Unix' │ │ │ │ -BA75B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA75C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA75D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA75F Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA761 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA765 CRC 42AB8589 (1118537097) │ │ │ │ -BA769 Compressed Size 000040B8 (16568) │ │ │ │ -BA76D Uncompressed Size 00013397 (78743) │ │ │ │ -BA771 Filename Length 0016 (22) │ │ │ │ -BA773 Extra Length 0018 (24) │ │ │ │ -BA775 Comment Length 0000 (0) │ │ │ │ -BA777 Disk Start 0000 (0) │ │ │ │ -BA779 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA77B Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA77F Local Header Offset 000795D0 (497104) │ │ │ │ -BA783 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA783: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA799 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA79B Length 0005 (5) │ │ │ │ -BA79D Flags 01 (1) 'Modification' │ │ │ │ -BA79E Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA7A2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA7A4 Length 000B (11) │ │ │ │ -BA7A6 Version 01 (1) │ │ │ │ -BA7A7 UID Size 04 (4) │ │ │ │ -BA7A8 UID 00000000 (0) │ │ │ │ -BA7AC GID Size 04 (4) │ │ │ │ -BA7AD GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA7B1 CENTRAL HEADER #72 02014B50 (33639248) │ │ │ │ -BA7B5 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA7B6 Created OS 03 (3) 'Unix' │ │ │ │ -BA7B7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA7B8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA7B9 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA7BB Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA7BD Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA7C1 CRC C0168E2C (3222703660) │ │ │ │ -BA7C5 Compressed Size 00003E83 (16003) │ │ │ │ -BA7C9 Uncompressed Size 0001C17B (115067) │ │ │ │ -BA7CD Filename Length 0019 (25) │ │ │ │ -BA7CF Extra Length 0018 (24) │ │ │ │ -BA7D1 Comment Length 0000 (0) │ │ │ │ -BA7D3 Disk Start 0000 (0) │ │ │ │ -BA7D5 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA7D7 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA7DB Local Header Offset 0007D6D8 (513752) │ │ │ │ -BA7DF Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA7DF: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA7F8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA7FA Length 0005 (5) │ │ │ │ -BA7FC Flags 01 (1) 'Modification' │ │ │ │ -BA7FD Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA801 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA803 Length 000B (11) │ │ │ │ -BA805 Version 01 (1) │ │ │ │ -BA806 UID Size 04 (4) │ │ │ │ -BA807 UID 00000000 (0) │ │ │ │ -BA80B GID Size 04 (4) │ │ │ │ -BA80C GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA810 CENTRAL HEADER #73 02014B50 (33639248) │ │ │ │ -BA814 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA815 Created OS 03 (3) 'Unix' │ │ │ │ -BA816 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA817 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA818 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA81A Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA81C Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA820 CRC B11E2924 (2971543844) │ │ │ │ -BA824 Compressed Size 0000088C (2188) │ │ │ │ -BA828 Uncompressed Size 0000362E (13870) │ │ │ │ -BA82C Filename Length 0011 (17) │ │ │ │ -BA82E Extra Length 0018 (24) │ │ │ │ -BA830 Comment Length 0000 (0) │ │ │ │ -BA832 Disk Start 0000 (0) │ │ │ │ -BA834 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA836 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA83A Local Header Offset 000815AE (529838) │ │ │ │ -BA83E Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA83E: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA84F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA851 Length 0005 (5) │ │ │ │ -BA853 Flags 01 (1) 'Modification' │ │ │ │ -BA854 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA858 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA85A Length 000B (11) │ │ │ │ -BA85C Version 01 (1) │ │ │ │ -BA85D UID Size 04 (4) │ │ │ │ -BA85E UID 00000000 (0) │ │ │ │ -BA862 GID Size 04 (4) │ │ │ │ -BA863 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA867 CENTRAL HEADER #74 02014B50 (33639248) │ │ │ │ -BA86B Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA86C Created OS 03 (3) 'Unix' │ │ │ │ -BA86D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA86E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA86F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA871 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA873 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA877 CRC B4CB7C77 (3033234551) │ │ │ │ -BA87B Compressed Size 000051C3 (20931) │ │ │ │ -BA87F Uncompressed Size 0001FBF7 (130039) │ │ │ │ -BA883 Filename Length 0015 (21) │ │ │ │ -BA885 Extra Length 0018 (24) │ │ │ │ -BA887 Comment Length 0000 (0) │ │ │ │ -BA889 Disk Start 0000 (0) │ │ │ │ -BA88B Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA88D Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA891 Local Header Offset 00081E85 (532101) │ │ │ │ -BA895 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA895: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA8AA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA8AC Length 0005 (5) │ │ │ │ -BA8AE Flags 01 (1) 'Modification' │ │ │ │ -BA8AF Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA8B3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA8B5 Length 000B (11) │ │ │ │ -BA8B7 Version 01 (1) │ │ │ │ -BA8B8 UID Size 04 (4) │ │ │ │ -BA8B9 UID 00000000 (0) │ │ │ │ -BA8BD GID Size 04 (4) │ │ │ │ -BA8BE GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA8C2 CENTRAL HEADER #75 02014B50 (33639248) │ │ │ │ -BA8C6 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA8C7 Created OS 03 (3) 'Unix' │ │ │ │ -BA8C8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA8C9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA8CA General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA8CC Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA8CE Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA8D2 CRC C7EDAA9C (3354241692) │ │ │ │ -BA8D6 Compressed Size 00001C42 (7234) │ │ │ │ -BA8DA Uncompressed Size 00008AC2 (35522) │ │ │ │ -BA8DE Filename Length 0019 (25) │ │ │ │ -BA8E0 Extra Length 0018 (24) │ │ │ │ -BA8E2 Comment Length 0000 (0) │ │ │ │ -BA8E4 Disk Start 0000 (0) │ │ │ │ -BA8E6 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA8E8 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA8EC Local Header Offset 00087097 (553111) │ │ │ │ -BA8F0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA8F0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA909 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA90B Length 0005 (5) │ │ │ │ -BA90D Flags 01 (1) 'Modification' │ │ │ │ -BA90E Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA912 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA914 Length 000B (11) │ │ │ │ -BA916 Version 01 (1) │ │ │ │ -BA917 UID Size 04 (4) │ │ │ │ -BA918 UID 00000000 (0) │ │ │ │ -BA91C GID Size 04 (4) │ │ │ │ -BA91D GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA921 CENTRAL HEADER #76 02014B50 (33639248) │ │ │ │ -BA925 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA926 Created OS 03 (3) 'Unix' │ │ │ │ -BA927 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA928 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA929 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA92B Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA92D Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA931 CRC 453D1C3A (1161632826) │ │ │ │ -BA935 Compressed Size 00000D95 (3477) │ │ │ │ -BA939 Uncompressed Size 00002E9F (11935) │ │ │ │ -BA93D Filename Length 0018 (24) │ │ │ │ -BA93F Extra Length 0018 (24) │ │ │ │ -BA941 Comment Length 0000 (0) │ │ │ │ -BA943 Disk Start 0000 (0) │ │ │ │ -BA945 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA947 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA94B Local Header Offset 00088D2C (560428) │ │ │ │ -BA94F Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA94F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA967 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA969 Length 0005 (5) │ │ │ │ -BA96B Flags 01 (1) 'Modification' │ │ │ │ -BA96C Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA970 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA972 Length 000B (11) │ │ │ │ -BA974 Version 01 (1) │ │ │ │ -BA975 UID Size 04 (4) │ │ │ │ -BA976 UID 00000000 (0) │ │ │ │ -BA97A GID Size 04 (4) │ │ │ │ -BA97B GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA97F CENTRAL HEADER #77 02014B50 (33639248) │ │ │ │ -BA983 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA984 Created OS 03 (3) 'Unix' │ │ │ │ -BA985 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA986 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA987 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA989 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA98B Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA98F CRC E5DE5170 (3856552304) │ │ │ │ -BA993 Compressed Size 000001DF (479) │ │ │ │ -BA997 Uncompressed Size 00000323 (803) │ │ │ │ -BA99B Filename Length 0011 (17) │ │ │ │ -BA99D Extra Length 0018 (24) │ │ │ │ -BA99F Comment Length 0000 (0) │ │ │ │ -BA9A1 Disk Start 0000 (0) │ │ │ │ -BA9A3 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA9A5 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BA9A9 Local Header Offset 00089B13 (563987) │ │ │ │ -BA9AD Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBA9AD: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BA9BE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BA9C0 Length 0005 (5) │ │ │ │ -BA9C2 Flags 01 (1) 'Modification' │ │ │ │ -BA9C3 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BA9C7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BA9C9 Length 000B (11) │ │ │ │ -BA9CB Version 01 (1) │ │ │ │ -BA9CC UID Size 04 (4) │ │ │ │ -BA9CD UID 00000000 (0) │ │ │ │ -BA9D1 GID Size 04 (4) │ │ │ │ -BA9D2 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BA9D6 CENTRAL HEADER #78 02014B50 (33639248) │ │ │ │ -BA9DA Created Zip Spec 3D (61) '6.1' │ │ │ │ -BA9DB Created OS 03 (3) 'Unix' │ │ │ │ -BA9DC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BA9DD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BA9DE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BA9E0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BA9E2 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BA9E6 CRC AA4987E4 (2856945636) │ │ │ │ -BA9EA Compressed Size 000006BD (1725) │ │ │ │ -BA9EE Uncompressed Size 0000141E (5150) │ │ │ │ -BA9F2 Filename Length 0019 (25) │ │ │ │ -BA9F4 Extra Length 0018 (24) │ │ │ │ -BA9F6 Comment Length 0000 (0) │ │ │ │ -BA9F8 Disk Start 0000 (0) │ │ │ │ -BA9FA Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BA9FC Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BAA00 Local Header Offset 00089D3D (564541) │ │ │ │ -BAA04 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBAA04: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BAA1D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BAA1F Length 0005 (5) │ │ │ │ -BAA21 Flags 01 (1) 'Modification' │ │ │ │ -BAA22 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BAA26 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BAA28 Length 000B (11) │ │ │ │ -BAA2A Version 01 (1) │ │ │ │ -BAA2B UID Size 04 (4) │ │ │ │ -BAA2C UID 00000000 (0) │ │ │ │ -BAA30 GID Size 04 (4) │ │ │ │ -BAA31 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BAA35 CENTRAL HEADER #79 02014B50 (33639248) │ │ │ │ -BAA39 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BAA3A Created OS 03 (3) 'Unix' │ │ │ │ -BAA3B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BAA3C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BAA3D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BAA3F Compression Method 0008 (8) 'Deflated' │ │ │ │ -BAA41 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BAA45 CRC CF6FE96A (3480217962) │ │ │ │ -BAA49 Compressed Size 00001B8B (7051) │ │ │ │ -BAA4D Uncompressed Size 00009F5F (40799) │ │ │ │ -BAA51 Filename Length 0018 (24) │ │ │ │ -BAA53 Extra Length 0018 (24) │ │ │ │ -BAA55 Comment Length 0000 (0) │ │ │ │ -BAA57 Disk Start 0000 (0) │ │ │ │ -BAA59 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BAA5B Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BAA5F Local Header Offset 0008A44D (566349) │ │ │ │ -BAA63 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBAA63: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BAA7B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BAA7D Length 0005 (5) │ │ │ │ -BAA7F Flags 01 (1) 'Modification' │ │ │ │ -BAA80 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BAA84 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BAA86 Length 000B (11) │ │ │ │ -BAA88 Version 01 (1) │ │ │ │ -BAA89 UID Size 04 (4) │ │ │ │ -BAA8A UID 00000000 (0) │ │ │ │ -BAA8E GID Size 04 (4) │ │ │ │ -BAA8F GID 00000000 (0) │ │ │ │ - │ │ │ │ -BAA93 CENTRAL HEADER #80 02014B50 (33639248) │ │ │ │ -BAA97 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BAA98 Created OS 03 (3) 'Unix' │ │ │ │ -BAA99 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BAA9A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BAA9B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BAA9D Compression Method 0008 (8) 'Deflated' │ │ │ │ -BAA9F Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BAAA3 CRC 3D2C86BB (1026328251) │ │ │ │ -BAAA7 Compressed Size 00001702 (5890) │ │ │ │ -BAAAB Uncompressed Size 00008B12 (35602) │ │ │ │ -BAAAF Filename Length 0012 (18) │ │ │ │ -BAAB1 Extra Length 0018 (24) │ │ │ │ -BAAB3 Comment Length 0000 (0) │ │ │ │ -BAAB5 Disk Start 0000 (0) │ │ │ │ -BAAB7 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BAAB9 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BAABD Local Header Offset 0008C02A (573482) │ │ │ │ -BAAC1 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBAAC1: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BAAD3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BAAD5 Length 0005 (5) │ │ │ │ -BAAD7 Flags 01 (1) 'Modification' │ │ │ │ -BAAD8 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BAADC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BAADE Length 000B (11) │ │ │ │ -BAAE0 Version 01 (1) │ │ │ │ -BAAE1 UID Size 04 (4) │ │ │ │ -BAAE2 UID 00000000 (0) │ │ │ │ -BAAE6 GID Size 04 (4) │ │ │ │ -BAAE7 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BAAEB CENTRAL HEADER #81 02014B50 (33639248) │ │ │ │ -BAAEF Created Zip Spec 3D (61) '6.1' │ │ │ │ -BAAF0 Created OS 03 (3) 'Unix' │ │ │ │ -BAAF1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BAAF2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BAAF3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BAAF5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BAAF7 Modification Time 5C734D4E (1551060302) 'Thu Mar 19 09:42:28 2026' │ │ │ │ -BAAFB CRC 27EFA83C (670017596) │ │ │ │ -BAAFF Compressed Size 00001E0D (7693) │ │ │ │ -BAB03 Uncompressed Size 00008803 (34819) │ │ │ │ -BAB07 Filename Length 0016 (22) │ │ │ │ -BAB09 Extra Length 0018 (24) │ │ │ │ -BAB0B Comment Length 0000 (0) │ │ │ │ -BAB0D Disk Start 0000 (0) │ │ │ │ -BAB0F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BAB11 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BAB15 Local Header Offset 0008D778 (579448) │ │ │ │ -BAB19 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBAB19: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BAB2F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BAB31 Length 0005 (5) │ │ │ │ -BAB33 Flags 01 (1) 'Modification' │ │ │ │ -BAB34 Modification Time 69BBC505 (1773913349) 'Thu Mar 19 09:42:29 2026' │ │ │ │ -BAB38 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BAB3A Length 000B (11) │ │ │ │ -BAB3C Version 01 (1) │ │ │ │ -BAB3D UID Size 04 (4) │ │ │ │ -BAB3E UID 00000000 (0) │ │ │ │ -BAB42 GID Size 04 (4) │ │ │ │ -BAB43 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BAB47 CENTRAL HEADER #82 02014B50 (33639248) │ │ │ │ -BAB4B Created Zip Spec 3D (61) '6.1' │ │ │ │ -BAB4C Created OS 03 (3) 'Unix' │ │ │ │ -BAB4D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BAB4E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BAB4F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BAB51 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BAB53 Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BAB57 CRC 6E962EB6 (1855336118) │ │ │ │ -BAB5B Compressed Size 000029A8 (10664) │ │ │ │ -BAB5F Uncompressed Size 0000D04F (53327) │ │ │ │ -BAB63 Filename Length 001A (26) │ │ │ │ -BAB65 Extra Length 0018 (24) │ │ │ │ -BAB67 Comment Length 0000 (0) │ │ │ │ -BAB69 Disk Start 0000 (0) │ │ │ │ -BAB6B Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BAB6D Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BAB71 Local Header Offset 0008F5D5 (587221) │ │ │ │ -BAB75 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBAB75: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BAB8F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BAB91 Length 0005 (5) │ │ │ │ -BAB93 Flags 01 (1) 'Modification' │ │ │ │ -BAB94 Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BAB98 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BAB9A Length 000B (11) │ │ │ │ -BAB9C Version 01 (1) │ │ │ │ -BAB9D UID Size 04 (4) │ │ │ │ -BAB9E UID 00000000 (0) │ │ │ │ -BABA2 GID Size 04 (4) │ │ │ │ -BABA3 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BABA7 CENTRAL HEADER #83 02014B50 (33639248) │ │ │ │ -BABAB Created Zip Spec 3D (61) '6.1' │ │ │ │ -BABAC Created OS 03 (3) 'Unix' │ │ │ │ -BABAD Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BABAE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BABAF General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BABB1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BABB3 Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BABB7 CRC E9ED2192 (3924631954) │ │ │ │ -BABBB Compressed Size 000009AA (2474) │ │ │ │ -BABBF Uncompressed Size 00001DB6 (7606) │ │ │ │ -BABC3 Filename Length 0018 (24) │ │ │ │ -BABC5 Extra Length 0018 (24) │ │ │ │ -BABC7 Comment Length 0000 (0) │ │ │ │ -BABC9 Disk Start 0000 (0) │ │ │ │ -BABCB Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BABCD Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BABD1 Local Header Offset 00091FD1 (597969) │ │ │ │ -BABD5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBABD5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BABED Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BABEF Length 0005 (5) │ │ │ │ -BABF1 Flags 01 (1) 'Modification' │ │ │ │ -BABF2 Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BABF6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BABF8 Length 000B (11) │ │ │ │ -BABFA Version 01 (1) │ │ │ │ -BABFB UID Size 04 (4) │ │ │ │ -BABFC UID 00000000 (0) │ │ │ │ -BAC00 GID Size 04 (4) │ │ │ │ -BAC01 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BAC05 CENTRAL HEADER #84 02014B50 (33639248) │ │ │ │ -BAC09 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BAC0A Created OS 03 (3) 'Unix' │ │ │ │ -BAC0B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BAC0C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BAC0D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BAC0F Compression Method 0008 (8) 'Deflated' │ │ │ │ -BAC11 Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BAC15 CRC F0556E9A (4032130714) │ │ │ │ -BAC19 Compressed Size 000152EE (86766) │ │ │ │ -BAC1D Uncompressed Size 000159F8 (88568) │ │ │ │ -BAC21 Filename Length 001E (30) │ │ │ │ -BAC23 Extra Length 0018 (24) │ │ │ │ -BAC25 Comment Length 0000 (0) │ │ │ │ -BAC27 Disk Start 0000 (0) │ │ │ │ -BAC29 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BAC2B Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BAC2F Local Header Offset 000929CD (600525) │ │ │ │ -BAC33 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBAC33: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BAC51 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BAC53 Length 0005 (5) │ │ │ │ -BAC55 Flags 01 (1) 'Modification' │ │ │ │ -BAC56 Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BAC5A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BAC5C Length 000B (11) │ │ │ │ -BAC5E Version 01 (1) │ │ │ │ -BAC5F UID Size 04 (4) │ │ │ │ -BAC60 UID 00000000 (0) │ │ │ │ -BAC64 GID Size 04 (4) │ │ │ │ -BAC65 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BAC69 CENTRAL HEADER #85 02014B50 (33639248) │ │ │ │ -BAC6D Created Zip Spec 3D (61) '6.1' │ │ │ │ -BAC6E Created OS 03 (3) 'Unix' │ │ │ │ -BAC6F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BAC70 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BAC71 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BAC73 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BAC75 Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BAC79 CRC F5E2129F (4125233823) │ │ │ │ -BAC7D Compressed Size 000016BC (5820) │ │ │ │ -BAC81 Uncompressed Size 000016CD (5837) │ │ │ │ -BAC85 Filename Length 0015 (21) │ │ │ │ -BAC87 Extra Length 0018 (24) │ │ │ │ -BAC89 Comment Length 0000 (0) │ │ │ │ -BAC8B Disk Start 0000 (0) │ │ │ │ -BAC8D Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BAC8F Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BAC93 Local Header Offset 000A7D13 (687379) │ │ │ │ -BAC97 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBAC97: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BACAC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BACAE Length 0005 (5) │ │ │ │ -BACB0 Flags 01 (1) 'Modification' │ │ │ │ -BACB1 Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BACB5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BACB7 Length 000B (11) │ │ │ │ -BACB9 Version 01 (1) │ │ │ │ -BACBA UID Size 04 (4) │ │ │ │ -BACBB UID 00000000 (0) │ │ │ │ -BACBF GID Size 04 (4) │ │ │ │ -BACC0 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BACC4 CENTRAL HEADER #86 02014B50 (33639248) │ │ │ │ -BACC8 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BACC9 Created OS 03 (3) 'Unix' │ │ │ │ -BACCA Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BACCB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BACCC General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BACCE Compression Method 0008 (8) 'Deflated' │ │ │ │ -BACD0 Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BACD4 CRC F5E2129F (4125233823) │ │ │ │ -BACD8 Compressed Size 000016BC (5820) │ │ │ │ -BACDC Uncompressed Size 000016CD (5837) │ │ │ │ -BACE0 Filename Length 001C (28) │ │ │ │ -BACE2 Extra Length 0018 (24) │ │ │ │ -BACE4 Comment Length 0000 (0) │ │ │ │ -BACE6 Disk Start 0000 (0) │ │ │ │ -BACE8 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BACEA Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BACEE Local Header Offset 000A941E (693278) │ │ │ │ -BACF2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBACF2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BAD0E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BAD10 Length 0005 (5) │ │ │ │ -BAD12 Flags 01 (1) 'Modification' │ │ │ │ -BAD13 Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BAD17 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BAD19 Length 000B (11) │ │ │ │ -BAD1B Version 01 (1) │ │ │ │ -BAD1C UID Size 04 (4) │ │ │ │ -BAD1D UID 00000000 (0) │ │ │ │ -BAD21 GID Size 04 (4) │ │ │ │ -BAD22 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BAD26 CENTRAL HEADER #87 02014B50 (33639248) │ │ │ │ -BAD2A Created Zip Spec 3D (61) '6.1' │ │ │ │ -BAD2B Created OS 03 (3) 'Unix' │ │ │ │ -BAD2C Extract Zip Spec 0A (10) '1.0' │ │ │ │ -BAD2D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BAD2E General Purpose Flag 0000 (0) │ │ │ │ -BAD30 Compression Method 0000 (0) 'Stored' │ │ │ │ -BAD32 Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BAD36 CRC FC95F24B (4237685323) │ │ │ │ -BAD3A Compressed Size 00001B84 (7044) │ │ │ │ -BAD3E Uncompressed Size 00001B84 (7044) │ │ │ │ -BAD42 Filename Length 0016 (22) │ │ │ │ -BAD44 Extra Length 0018 (24) │ │ │ │ -BAD46 Comment Length 0000 (0) │ │ │ │ -BAD48 Disk Start 0000 (0) │ │ │ │ -BAD4A Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BAD4C Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BAD50 Local Header Offset 000AAB30 (699184) │ │ │ │ -BAD54 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBAD54: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BAD6A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BAD6C Length 0005 (5) │ │ │ │ -BAD6E Flags 01 (1) 'Modification' │ │ │ │ -BAD6F Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BAD73 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BAD75 Length 000B (11) │ │ │ │ -BAD77 Version 01 (1) │ │ │ │ -BAD78 UID Size 04 (4) │ │ │ │ -BAD79 UID 00000000 (0) │ │ │ │ -BAD7D GID Size 04 (4) │ │ │ │ -BAD7E GID 00000000 (0) │ │ │ │ - │ │ │ │ -BAD82 CENTRAL HEADER #88 02014B50 (33639248) │ │ │ │ -BAD86 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BAD87 Created OS 03 (3) 'Unix' │ │ │ │ -BAD88 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -BAD89 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BAD8A General Purpose Flag 0000 (0) │ │ │ │ -BAD8C Compression Method 0000 (0) 'Stored' │ │ │ │ -BAD8E Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BAD92 CRC D0D71F86 (3503759238) │ │ │ │ -BAD96 Compressed Size 00000B7B (2939) │ │ │ │ -BAD9A Uncompressed Size 00000B7B (2939) │ │ │ │ -BAD9E Filename Length 0016 (22) │ │ │ │ -BADA0 Extra Length 0018 (24) │ │ │ │ -BADA2 Comment Length 0000 (0) │ │ │ │ -BADA4 Disk Start 0000 (0) │ │ │ │ -BADA6 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BADA8 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BADAC Local Header Offset 000AC704 (706308) │ │ │ │ -BADB0 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBADB0: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BADC6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BADC8 Length 0005 (5) │ │ │ │ -BADCA Flags 01 (1) 'Modification' │ │ │ │ -BADCB Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BADCF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BADD1 Length 000B (11) │ │ │ │ -BADD3 Version 01 (1) │ │ │ │ -BADD4 UID Size 04 (4) │ │ │ │ -BADD5 UID 00000000 (0) │ │ │ │ -BADD9 GID Size 04 (4) │ │ │ │ -BADDA GID 00000000 (0) │ │ │ │ - │ │ │ │ -BADDE CENTRAL HEADER #89 02014B50 (33639248) │ │ │ │ -BADE2 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BADE3 Created OS 03 (3) 'Unix' │ │ │ │ -BADE4 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -BADE5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BADE6 General Purpose Flag 0000 (0) │ │ │ │ -BADE8 Compression Method 0000 (0) 'Stored' │ │ │ │ -BADEA Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BADEE CRC FFF9C4D2 (4294558930) │ │ │ │ -BADF2 Compressed Size 0000138F (5007) │ │ │ │ -BADF6 Uncompressed Size 0000138F (5007) │ │ │ │ -BADFA Filename Length 0016 (22) │ │ │ │ -BADFC Extra Length 0018 (24) │ │ │ │ -BADFE Comment Length 0000 (0) │ │ │ │ -BAE00 Disk Start 0000 (0) │ │ │ │ -BAE02 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BAE04 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BAE08 Local Header Offset 000AD2CF (709327) │ │ │ │ -BAE0C Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBAE0C: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BAE22 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BAE24 Length 0005 (5) │ │ │ │ -BAE26 Flags 01 (1) 'Modification' │ │ │ │ -BAE27 Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BAE2B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BAE2D Length 000B (11) │ │ │ │ -BAE2F Version 01 (1) │ │ │ │ -BAE30 UID Size 04 (4) │ │ │ │ -BAE31 UID 00000000 (0) │ │ │ │ -BAE35 GID Size 04 (4) │ │ │ │ -BAE36 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BAE3A CENTRAL HEADER #90 02014B50 (33639248) │ │ │ │ -BAE3E Created Zip Spec 3D (61) '6.1' │ │ │ │ -BAE3F Created OS 03 (3) 'Unix' │ │ │ │ -BAE40 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -BAE41 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BAE42 General Purpose Flag 0000 (0) │ │ │ │ -BAE44 Compression Method 0000 (0) 'Stored' │ │ │ │ -BAE46 Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BAE4A CRC A1037E8E (2701360782) │ │ │ │ -BAE4E Compressed Size 0000145E (5214) │ │ │ │ -BAE52 Uncompressed Size 0000145E (5214) │ │ │ │ -BAE56 Filename Length 0016 (22) │ │ │ │ -BAE58 Extra Length 0018 (24) │ │ │ │ -BAE5A Comment Length 0000 (0) │ │ │ │ -BAE5C Disk Start 0000 (0) │ │ │ │ -BAE5E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BAE60 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BAE64 Local Header Offset 000AE6AE (714414) │ │ │ │ -BAE68 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBAE68: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BAE7E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BAE80 Length 0005 (5) │ │ │ │ -BAE82 Flags 01 (1) 'Modification' │ │ │ │ -BAE83 Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BAE87 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BAE89 Length 000B (11) │ │ │ │ -BAE8B Version 01 (1) │ │ │ │ -BAE8C UID Size 04 (4) │ │ │ │ -BAE8D UID 00000000 (0) │ │ │ │ -BAE91 GID Size 04 (4) │ │ │ │ -BAE92 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BAE96 CENTRAL HEADER #91 02014B50 (33639248) │ │ │ │ -BAE9A Created Zip Spec 3D (61) '6.1' │ │ │ │ -BAE9B Created OS 03 (3) 'Unix' │ │ │ │ -BAE9C Extract Zip Spec 0A (10) '1.0' │ │ │ │ -BAE9D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BAE9E General Purpose Flag 0000 (0) │ │ │ │ -BAEA0 Compression Method 0000 (0) 'Stored' │ │ │ │ -BAEA2 Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BAEA6 CRC 5E9E64F1 (1587438833) │ │ │ │ -BAEAA Compressed Size 000008EC (2284) │ │ │ │ -BAEAE Uncompressed Size 000008EC (2284) │ │ │ │ -BAEB2 Filename Length 0016 (22) │ │ │ │ -BAEB4 Extra Length 0018 (24) │ │ │ │ -BAEB6 Comment Length 0000 (0) │ │ │ │ -BAEB8 Disk Start 0000 (0) │ │ │ │ -BAEBA Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BAEBC Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BAEC0 Local Header Offset 000AFB5C (719708) │ │ │ │ -BAEC4 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBAEC4: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BAEDA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BAEDC Length 0005 (5) │ │ │ │ -BAEDE Flags 01 (1) 'Modification' │ │ │ │ -BAEDF Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BAEE3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BAEE5 Length 000B (11) │ │ │ │ -BAEE7 Version 01 (1) │ │ │ │ -BAEE8 UID Size 04 (4) │ │ │ │ -BAEE9 UID 00000000 (0) │ │ │ │ -BAEED GID Size 04 (4) │ │ │ │ -BAEEE GID 00000000 (0) │ │ │ │ - │ │ │ │ -BAEF2 CENTRAL HEADER #92 02014B50 (33639248) │ │ │ │ -BAEF6 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BAEF7 Created OS 03 (3) 'Unix' │ │ │ │ -BAEF8 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -BAEF9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BAEFA General Purpose Flag 0000 (0) │ │ │ │ -BAEFC Compression Method 0000 (0) 'Stored' │ │ │ │ -BAEFE Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BAF02 CRC 42E340AB (1122189483) │ │ │ │ -BAF06 Compressed Size 00001F2E (7982) │ │ │ │ -BAF0A Uncompressed Size 00001F2E (7982) │ │ │ │ -BAF0E Filename Length 001E (30) │ │ │ │ -BAF10 Extra Length 0018 (24) │ │ │ │ -BAF12 Comment Length 0000 (0) │ │ │ │ -BAF14 Disk Start 0000 (0) │ │ │ │ -BAF16 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BAF18 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BAF1C Local Header Offset 000B0498 (722072) │ │ │ │ -BAF20 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBAF20: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BAF3E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BAF40 Length 0005 (5) │ │ │ │ -BAF42 Flags 01 (1) 'Modification' │ │ │ │ -BAF43 Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BAF47 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BAF49 Length 000B (11) │ │ │ │ -BAF4B Version 01 (1) │ │ │ │ -BAF4C UID Size 04 (4) │ │ │ │ -BAF4D UID 00000000 (0) │ │ │ │ -BAF51 GID Size 04 (4) │ │ │ │ -BAF52 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BAF56 CENTRAL HEADER #93 02014B50 (33639248) │ │ │ │ -BAF5A Created Zip Spec 3D (61) '6.1' │ │ │ │ -BAF5B Created OS 03 (3) 'Unix' │ │ │ │ -BAF5C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BAF5D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BAF5E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BAF60 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BAF62 Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BAF66 CRC 02454CAB (38096043) │ │ │ │ -BAF6A Compressed Size 00003D6D (15725) │ │ │ │ -BAF6E Uncompressed Size 0001664F (91727) │ │ │ │ -BAF72 Filename Length 001A (26) │ │ │ │ -BAF74 Extra Length 0018 (24) │ │ │ │ -BAF76 Comment Length 0000 (0) │ │ │ │ -BAF78 Disk Start 0000 (0) │ │ │ │ -BAF7A Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BAF7C Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BAF80 Local Header Offset 000B241E (730142) │ │ │ │ -BAF84 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBAF84: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BAF9E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BAFA0 Length 0005 (5) │ │ │ │ -BAFA2 Flags 01 (1) 'Modification' │ │ │ │ -BAFA3 Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BAFA7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BAFA9 Length 000B (11) │ │ │ │ -BAFAB Version 01 (1) │ │ │ │ -BAFAC UID Size 04 (4) │ │ │ │ -BAFAD UID 00000000 (0) │ │ │ │ -BAFB1 GID Size 04 (4) │ │ │ │ -BAFB2 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BAFB6 CENTRAL HEADER #94 02014B50 (33639248) │ │ │ │ -BAFBA Created Zip Spec 3D (61) '6.1' │ │ │ │ -BAFBB Created OS 03 (3) 'Unix' │ │ │ │ -BAFBC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BAFBD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BAFBE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BAFC0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -BAFC2 Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BAFC6 CRC 3778562B (930633259) │ │ │ │ -BAFCA Compressed Size 000029CF (10703) │ │ │ │ -BAFCE Uncompressed Size 0000BB39 (47929) │ │ │ │ -BAFD2 Filename Length 0018 (24) │ │ │ │ -BAFD4 Extra Length 0018 (24) │ │ │ │ -BAFD6 Comment Length 0000 (0) │ │ │ │ -BAFD8 Disk Start 0000 (0) │ │ │ │ -BAFDA Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BAFDC Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BAFE0 Local Header Offset 000B61DF (745951) │ │ │ │ -BAFE4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBAFE4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BAFFC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BAFFE Length 0005 (5) │ │ │ │ -BB000 Flags 01 (1) 'Modification' │ │ │ │ -BB001 Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BB005 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BB007 Length 000B (11) │ │ │ │ -BB009 Version 01 (1) │ │ │ │ -BB00A UID Size 04 (4) │ │ │ │ -BB00B UID 00000000 (0) │ │ │ │ -BB00F GID Size 04 (4) │ │ │ │ -BB010 GID 00000000 (0) │ │ │ │ - │ │ │ │ -BB014 CENTRAL HEADER #95 02014B50 (33639248) │ │ │ │ -BB018 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BB019 Created OS 03 (3) 'Unix' │ │ │ │ -BB01A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BB01B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BB01C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BB01E Compression Method 0008 (8) 'Deflated' │ │ │ │ -BB020 Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BB024 CRC DCB3B516 (3702764822) │ │ │ │ -BB028 Compressed Size 000000AE (174) │ │ │ │ -BB02C Uncompressed Size 000000FC (252) │ │ │ │ -BB030 Filename Length 0016 (22) │ │ │ │ -BB032 Extra Length 0018 (24) │ │ │ │ -BB034 Comment Length 0000 (0) │ │ │ │ -BB036 Disk Start 0000 (0) │ │ │ │ -BB038 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BB03A Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BB03E Local Header Offset 000B8C00 (756736) │ │ │ │ -BB042 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBB042: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BB058 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BB05A Length 0005 (5) │ │ │ │ -BB05C Flags 01 (1) 'Modification' │ │ │ │ -BB05D Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BB061 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BB063 Length 000B (11) │ │ │ │ -BB065 Version 01 (1) │ │ │ │ -BB066 UID Size 04 (4) │ │ │ │ -BB067 UID 00000000 (0) │ │ │ │ -BB06B GID Size 04 (4) │ │ │ │ -BB06C GID 00000000 (0) │ │ │ │ - │ │ │ │ -BB070 CENTRAL HEADER #96 02014B50 (33639248) │ │ │ │ -BB074 Created Zip Spec 3D (61) '6.1' │ │ │ │ -BB075 Created OS 03 (3) 'Unix' │ │ │ │ -BB076 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -BB077 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -BB078 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -BB07A Compression Method 0008 (8) 'Deflated' │ │ │ │ -BB07C Modification Time 5C734D4F (1551060303) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BB080 CRC 58439733 (1480824627) │ │ │ │ -BB084 Compressed Size 00000077 (119) │ │ │ │ -BB088 Uncompressed Size 000000A2 (162) │ │ │ │ -BB08C Filename Length 002D (45) │ │ │ │ -BB08E Extra Length 0018 (24) │ │ │ │ -BB090 Comment Length 0000 (0) │ │ │ │ -BB092 Disk Start 0000 (0) │ │ │ │ -BB094 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -BB096 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -BB09A Local Header Offset 000B8CFE (756990) │ │ │ │ -BB09E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xBB09E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -BB0CB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -BB0CD Length 0005 (5) │ │ │ │ -BB0CF Flags 01 (1) 'Modification' │ │ │ │ -BB0D0 Modification Time 69BBC506 (1773913350) 'Thu Mar 19 09:42:30 2026' │ │ │ │ -BB0D4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -BB0D6 Length 000B (11) │ │ │ │ -BB0D8 Version 01 (1) │ │ │ │ -BB0D9 UID Size 04 (4) │ │ │ │ -BB0DA UID 00000000 (0) │ │ │ │ -BB0DE GID Size 04 (4) │ │ │ │ -BB0DF GID 00000000 (0) │ │ │ │ - │ │ │ │ -BB0E3 END CENTRAL HEADER 06054B50 (101010256) │ │ │ │ -BB0E7 Number of this disk 0000 (0) │ │ │ │ -BB0E9 Central Dir Disk no 0000 (0) │ │ │ │ -BB0EB Entries in this disk 0060 (96) │ │ │ │ -BB0ED Total Entries 0060 (96) │ │ │ │ -BB0EF Size of Central Dir 00002307 (8967) │ │ │ │ -BB0F3 Offset to Central Dir 000B8DDC (757212) │ │ │ │ -BB0F7 Comment Length 0000 (0) │ │ │ │ +B8D27 LOCAL HEADER #96 04034B50 (67324752) │ │ │ │ +B8D2B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8D2C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8D2D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8D2F Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8D31 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B8D35 CRC 58439733 (1480824627) │ │ │ │ +B8D39 Compressed Size 00000077 (119) │ │ │ │ +B8D3D Uncompressed Size 000000A2 (162) │ │ │ │ +B8D41 Filename Length 002D (45) │ │ │ │ +B8D43 Extra Length 001C (28) │ │ │ │ +B8D45 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8D45: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8D72 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8D74 Length 0009 (9) │ │ │ │ +B8D76 Flags 03 (3) 'Modification Access' │ │ │ │ +B8D77 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B8D7B Access Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B8D7F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8D81 Length 000B (11) │ │ │ │ +B8D83 Version 01 (1) │ │ │ │ +B8D84 UID Size 04 (4) │ │ │ │ +B8D85 UID 00000000 (0) │ │ │ │ +B8D89 GID Size 04 (4) │ │ │ │ +B8D8A GID 00000000 (0) │ │ │ │ +B8D8E PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ + │ │ │ │ +B8E05 CENTRAL HEADER #1 02014B50 (33639248) │ │ │ │ +B8E09 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8E0A Created OS 03 (3) 'Unix' │ │ │ │ +B8E0B Extract Zip Spec 0A (10) '1.0' │ │ │ │ +B8E0C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8E0D General Purpose Flag 0000 (0) │ │ │ │ +B8E0F Compression Method 0000 (0) 'Stored' │ │ │ │ +B8E11 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B8E15 CRC 2CAB616F (749429103) │ │ │ │ +B8E19 Compressed Size 00000014 (20) │ │ │ │ +B8E1D Uncompressed Size 00000014 (20) │ │ │ │ +B8E21 Filename Length 0008 (8) │ │ │ │ +B8E23 Extra Length 0018 (24) │ │ │ │ +B8E25 Comment Length 0000 (0) │ │ │ │ +B8E27 Disk Start 0000 (0) │ │ │ │ +B8E29 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8E2B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8E2F Local Header Offset 00000000 (0) │ │ │ │ +B8E33 Filename 'XXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8E33: Filename 'XXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8E3B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8E3D Length 0005 (5) │ │ │ │ +B8E3F Flags 01 (1) 'Modification' │ │ │ │ +B8E40 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B8E44 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8E46 Length 000B (11) │ │ │ │ +B8E48 Version 01 (1) │ │ │ │ +B8E49 UID Size 04 (4) │ │ │ │ +B8E4A UID 00000000 (0) │ │ │ │ +B8E4E GID Size 04 (4) │ │ │ │ +B8E4F GID 00000000 (0) │ │ │ │ + │ │ │ │ +B8E53 CENTRAL HEADER #2 02014B50 (33639248) │ │ │ │ +B8E57 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8E58 Created OS 03 (3) 'Unix' │ │ │ │ +B8E59 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8E5A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8E5B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8E5D Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8E5F Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B8E63 CRC C1930710 (3247638288) │ │ │ │ +B8E67 Compressed Size 00000D23 (3363) │ │ │ │ +B8E6B Uncompressed Size 00003933 (14643) │ │ │ │ +B8E6F Filename Length 001B (27) │ │ │ │ +B8E71 Extra Length 0018 (24) │ │ │ │ +B8E73 Comment Length 0000 (0) │ │ │ │ +B8E75 Disk Start 0000 (0) │ │ │ │ +B8E77 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8E79 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8E7D Local Header Offset 00000056 (86) │ │ │ │ +B8E81 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8E81: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8E9C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8E9E Length 0005 (5) │ │ │ │ +B8EA0 Flags 01 (1) 'Modification' │ │ │ │ +B8EA1 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B8EA5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8EA7 Length 000B (11) │ │ │ │ +B8EA9 Version 01 (1) │ │ │ │ +B8EAA UID Size 04 (4) │ │ │ │ +B8EAB UID 00000000 (0) │ │ │ │ +B8EAF GID Size 04 (4) │ │ │ │ +B8EB0 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B8EB4 CENTRAL HEADER #3 02014B50 (33639248) │ │ │ │ +B8EB8 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8EB9 Created OS 03 (3) 'Unix' │ │ │ │ +B8EBA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8EBB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8EBC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8EBE Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8EC0 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B8EC4 CRC 986340FB (2556641531) │ │ │ │ +B8EC8 Compressed Size 000015AF (5551) │ │ │ │ +B8ECC Uncompressed Size 00004602 (17922) │ │ │ │ +B8ED0 Filename Length 0014 (20) │ │ │ │ +B8ED2 Extra Length 0018 (24) │ │ │ │ +B8ED4 Comment Length 0000 (0) │ │ │ │ +B8ED6 Disk Start 0000 (0) │ │ │ │ +B8ED8 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8EDA Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8EDE Local Header Offset 00000DCE (3534) │ │ │ │ +B8EE2 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8EE2: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8EF6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8EF8 Length 0005 (5) │ │ │ │ +B8EFA Flags 01 (1) 'Modification' │ │ │ │ +B8EFB Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B8EFF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8F01 Length 000B (11) │ │ │ │ +B8F03 Version 01 (1) │ │ │ │ +B8F04 UID Size 04 (4) │ │ │ │ +B8F05 UID 00000000 (0) │ │ │ │ +B8F09 GID Size 04 (4) │ │ │ │ +B8F0A GID 00000000 (0) │ │ │ │ + │ │ │ │ +B8F0E CENTRAL HEADER #4 02014B50 (33639248) │ │ │ │ +B8F12 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8F13 Created OS 03 (3) 'Unix' │ │ │ │ +B8F14 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8F15 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8F16 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8F18 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8F1A Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B8F1E CRC 911C5DC4 (2434555332) │ │ │ │ +B8F22 Compressed Size 000006D3 (1747) │ │ │ │ +B8F26 Uncompressed Size 00001241 (4673) │ │ │ │ +B8F2A Filename Length 0013 (19) │ │ │ │ +B8F2C Extra Length 0018 (24) │ │ │ │ +B8F2E Comment Length 0000 (0) │ │ │ │ +B8F30 Disk Start 0000 (0) │ │ │ │ +B8F32 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8F34 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8F38 Local Header Offset 000023CB (9163) │ │ │ │ +B8F3C Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8F3C: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8F4F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8F51 Length 0005 (5) │ │ │ │ +B8F53 Flags 01 (1) 'Modification' │ │ │ │ +B8F54 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B8F58 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8F5A Length 000B (11) │ │ │ │ +B8F5C Version 01 (1) │ │ │ │ +B8F5D UID Size 04 (4) │ │ │ │ +B8F5E UID 00000000 (0) │ │ │ │ +B8F62 GID Size 04 (4) │ │ │ │ +B8F63 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B8F67 CENTRAL HEADER #5 02014B50 (33639248) │ │ │ │ +B8F6B Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8F6C Created OS 03 (3) 'Unix' │ │ │ │ +B8F6D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8F6E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8F6F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8F71 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8F73 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B8F77 CRC E6380C70 (3862432880) │ │ │ │ +B8F7B Compressed Size 00002E6D (11885) │ │ │ │ +B8F7F Uncompressed Size 0000D4B3 (54451) │ │ │ │ +B8F83 Filename Length 0014 (20) │ │ │ │ +B8F85 Extra Length 0018 (24) │ │ │ │ +B8F87 Comment Length 0000 (0) │ │ │ │ +B8F89 Disk Start 0000 (0) │ │ │ │ +B8F8B Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8F8D Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8F91 Local Header Offset 00002AEB (10987) │ │ │ │ +B8F95 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8F95: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B8FA9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B8FAB Length 0005 (5) │ │ │ │ +B8FAD Flags 01 (1) 'Modification' │ │ │ │ +B8FAE Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B8FB2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B8FB4 Length 000B (11) │ │ │ │ +B8FB6 Version 01 (1) │ │ │ │ +B8FB7 UID Size 04 (4) │ │ │ │ +B8FB8 UID 00000000 (0) │ │ │ │ +B8FBC GID Size 04 (4) │ │ │ │ +B8FBD GID 00000000 (0) │ │ │ │ + │ │ │ │ +B8FC1 CENTRAL HEADER #6 02014B50 (33639248) │ │ │ │ +B8FC5 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B8FC6 Created OS 03 (3) 'Unix' │ │ │ │ +B8FC7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B8FC8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B8FC9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B8FCB Compression Method 0008 (8) 'Deflated' │ │ │ │ +B8FCD Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B8FD1 CRC A85875F8 (2824369656) │ │ │ │ +B8FD5 Compressed Size 000003F0 (1008) │ │ │ │ +B8FD9 Uncompressed Size 00000876 (2166) │ │ │ │ +B8FDD Filename Length 0014 (20) │ │ │ │ +B8FDF Extra Length 0018 (24) │ │ │ │ +B8FE1 Comment Length 0000 (0) │ │ │ │ +B8FE3 Disk Start 0000 (0) │ │ │ │ +B8FE5 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B8FE7 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B8FEB Local Header Offset 000059A6 (22950) │ │ │ │ +B8FEF Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB8FEF: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9003 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9005 Length 0005 (5) │ │ │ │ +B9007 Flags 01 (1) 'Modification' │ │ │ │ +B9008 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B900C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B900E Length 000B (11) │ │ │ │ +B9010 Version 01 (1) │ │ │ │ +B9011 UID Size 04 (4) │ │ │ │ +B9012 UID 00000000 (0) │ │ │ │ +B9016 GID Size 04 (4) │ │ │ │ +B9017 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B901B CENTRAL HEADER #7 02014B50 (33639248) │ │ │ │ +B901F Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9020 Created OS 03 (3) 'Unix' │ │ │ │ +B9021 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9022 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9023 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9025 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9027 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B902B CRC F88D1943 (4169996611) │ │ │ │ +B902F Compressed Size 000001AD (429) │ │ │ │ +B9033 Uncompressed Size 000002FC (764) │ │ │ │ +B9037 Filename Length 0011 (17) │ │ │ │ +B9039 Extra Length 0018 (24) │ │ │ │ +B903B Comment Length 0000 (0) │ │ │ │ +B903D Disk Start 0000 (0) │ │ │ │ +B903F Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9041 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9045 Local Header Offset 00005DE4 (24036) │ │ │ │ +B9049 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9049: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B905A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B905C Length 0005 (5) │ │ │ │ +B905E Flags 01 (1) 'Modification' │ │ │ │ +B905F Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9063 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9065 Length 000B (11) │ │ │ │ +B9067 Version 01 (1) │ │ │ │ +B9068 UID Size 04 (4) │ │ │ │ +B9069 UID 00000000 (0) │ │ │ │ +B906D GID Size 04 (4) │ │ │ │ +B906E GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9072 CENTRAL HEADER #8 02014B50 (33639248) │ │ │ │ +B9076 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9077 Created OS 03 (3) 'Unix' │ │ │ │ +B9078 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9079 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B907A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B907C Compression Method 0008 (8) 'Deflated' │ │ │ │ +B907E Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9082 CRC E39B99C9 (3818625481) │ │ │ │ +B9086 Compressed Size 000020C9 (8393) │ │ │ │ +B908A Uncompressed Size 0000B4B0 (46256) │ │ │ │ +B908E Filename Length 001B (27) │ │ │ │ +B9090 Extra Length 0018 (24) │ │ │ │ +B9092 Comment Length 0000 (0) │ │ │ │ +B9094 Disk Start 0000 (0) │ │ │ │ +B9096 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9098 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B909C Local Header Offset 00005FDC (24540) │ │ │ │ +B90A0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB90A0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B90BB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B90BD Length 0005 (5) │ │ │ │ +B90BF Flags 01 (1) 'Modification' │ │ │ │ +B90C0 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B90C4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B90C6 Length 000B (11) │ │ │ │ +B90C8 Version 01 (1) │ │ │ │ +B90C9 UID Size 04 (4) │ │ │ │ +B90CA UID 00000000 (0) │ │ │ │ +B90CE GID Size 04 (4) │ │ │ │ +B90CF GID 00000000 (0) │ │ │ │ + │ │ │ │ +B90D3 CENTRAL HEADER #9 02014B50 (33639248) │ │ │ │ +B90D7 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B90D8 Created OS 03 (3) 'Unix' │ │ │ │ +B90D9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B90DA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B90DB General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B90DD Compression Method 0008 (8) 'Deflated' │ │ │ │ +B90DF Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B90E3 CRC 93C4F0D6 (2479157462) │ │ │ │ +B90E7 Compressed Size 00000E65 (3685) │ │ │ │ +B90EB Uncompressed Size 00003092 (12434) │ │ │ │ +B90EF Filename Length 001D (29) │ │ │ │ +B90F1 Extra Length 0018 (24) │ │ │ │ +B90F3 Comment Length 0000 (0) │ │ │ │ +B90F5 Disk Start 0000 (0) │ │ │ │ +B90F7 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B90F9 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B90FD Local Header Offset 000080FA (33018) │ │ │ │ +B9101 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9101: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B911E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9120 Length 0005 (5) │ │ │ │ +B9122 Flags 01 (1) 'Modification' │ │ │ │ +B9123 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9127 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9129 Length 000B (11) │ │ │ │ +B912B Version 01 (1) │ │ │ │ +B912C UID Size 04 (4) │ │ │ │ +B912D UID 00000000 (0) │ │ │ │ +B9131 GID Size 04 (4) │ │ │ │ +B9132 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9136 CENTRAL HEADER #10 02014B50 (33639248) │ │ │ │ +B913A Created Zip Spec 3D (61) '6.1' │ │ │ │ +B913B Created OS 03 (3) 'Unix' │ │ │ │ +B913C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B913D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B913E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9140 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9142 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9146 CRC 4F204BB6 (1327516598) │ │ │ │ +B914A Compressed Size 00000994 (2452) │ │ │ │ +B914E Uncompressed Size 00001D3D (7485) │ │ │ │ +B9152 Filename Length 0019 (25) │ │ │ │ +B9154 Extra Length 0018 (24) │ │ │ │ +B9156 Comment Length 0000 (0) │ │ │ │ +B9158 Disk Start 0000 (0) │ │ │ │ +B915A Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B915C Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9160 Local Header Offset 00008FB6 (36790) │ │ │ │ +B9164 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9164: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B917D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B917F Length 0005 (5) │ │ │ │ +B9181 Flags 01 (1) 'Modification' │ │ │ │ +B9182 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9186 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9188 Length 000B (11) │ │ │ │ +B918A Version 01 (1) │ │ │ │ +B918B UID Size 04 (4) │ │ │ │ +B918C UID 00000000 (0) │ │ │ │ +B9190 GID Size 04 (4) │ │ │ │ +B9191 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9195 CENTRAL HEADER #11 02014B50 (33639248) │ │ │ │ +B9199 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B919A Created OS 03 (3) 'Unix' │ │ │ │ +B919B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B919C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B919D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B919F Compression Method 0008 (8) 'Deflated' │ │ │ │ +B91A1 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B91A5 CRC CA8CA5BA (3398215098) │ │ │ │ +B91A9 Compressed Size 0000387F (14463) │ │ │ │ +B91AD Uncompressed Size 0000F7F4 (63476) │ │ │ │ +B91B1 Filename Length 0015 (21) │ │ │ │ +B91B3 Extra Length 0018 (24) │ │ │ │ +B91B5 Comment Length 0000 (0) │ │ │ │ +B91B7 Disk Start 0000 (0) │ │ │ │ +B91B9 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B91BB Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B91BF Local Header Offset 0000999D (39325) │ │ │ │ +B91C3 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB91C3: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B91D8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B91DA Length 0005 (5) │ │ │ │ +B91DC Flags 01 (1) 'Modification' │ │ │ │ +B91DD Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B91E1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B91E3 Length 000B (11) │ │ │ │ +B91E5 Version 01 (1) │ │ │ │ +B91E6 UID Size 04 (4) │ │ │ │ +B91E7 UID 00000000 (0) │ │ │ │ +B91EB GID Size 04 (4) │ │ │ │ +B91EC GID 00000000 (0) │ │ │ │ + │ │ │ │ +B91F0 CENTRAL HEADER #12 02014B50 (33639248) │ │ │ │ +B91F4 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B91F5 Created OS 03 (3) 'Unix' │ │ │ │ +B91F6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B91F7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B91F8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B91FA Compression Method 0008 (8) 'Deflated' │ │ │ │ +B91FC Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9200 CRC AFDB078C (2950367116) │ │ │ │ +B9204 Compressed Size 0000AB09 (43785) │ │ │ │ +B9208 Uncompressed Size 0003E0D3 (254163) │ │ │ │ +B920C Filename Length 0012 (18) │ │ │ │ +B920E Extra Length 0018 (24) │ │ │ │ +B9210 Comment Length 0000 (0) │ │ │ │ +B9212 Disk Start 0000 (0) │ │ │ │ +B9214 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9216 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B921A Local Header Offset 0000D26B (53867) │ │ │ │ +B921E Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB921E: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9230 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9232 Length 0005 (5) │ │ │ │ +B9234 Flags 01 (1) 'Modification' │ │ │ │ +B9235 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9239 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B923B Length 000B (11) │ │ │ │ +B923D Version 01 (1) │ │ │ │ +B923E UID Size 04 (4) │ │ │ │ +B923F UID 00000000 (0) │ │ │ │ +B9243 GID Size 04 (4) │ │ │ │ +B9244 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9248 CENTRAL HEADER #13 02014B50 (33639248) │ │ │ │ +B924C Created Zip Spec 3D (61) '6.1' │ │ │ │ +B924D Created OS 03 (3) 'Unix' │ │ │ │ +B924E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B924F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9250 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9252 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9254 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9258 CRC 32245BB0 (841243568) │ │ │ │ +B925C Compressed Size 00003B10 (15120) │ │ │ │ +B9260 Uncompressed Size 0001B46C (111724) │ │ │ │ +B9264 Filename Length 0015 (21) │ │ │ │ +B9266 Extra Length 0018 (24) │ │ │ │ +B9268 Comment Length 0000 (0) │ │ │ │ +B926A Disk Start 0000 (0) │ │ │ │ +B926C Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B926E Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9272 Local Header Offset 00017DC0 (97728) │ │ │ │ +B9276 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9276: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B928B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B928D Length 0005 (5) │ │ │ │ +B928F Flags 01 (1) 'Modification' │ │ │ │ +B9290 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9294 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9296 Length 000B (11) │ │ │ │ +B9298 Version 01 (1) │ │ │ │ +B9299 UID Size 04 (4) │ │ │ │ +B929A UID 00000000 (0) │ │ │ │ +B929E GID Size 04 (4) │ │ │ │ +B929F GID 00000000 (0) │ │ │ │ + │ │ │ │ +B92A3 CENTRAL HEADER #14 02014B50 (33639248) │ │ │ │ +B92A7 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B92A8 Created OS 03 (3) 'Unix' │ │ │ │ +B92A9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B92AA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B92AB General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B92AD Compression Method 0008 (8) 'Deflated' │ │ │ │ +B92AF Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B92B3 CRC DD758B13 (3715468051) │ │ │ │ +B92B7 Compressed Size 000091A1 (37281) │ │ │ │ +B92BB Uncompressed Size 0003D5E9 (251369) │ │ │ │ +B92BF Filename Length 0014 (20) │ │ │ │ +B92C1 Extra Length 0018 (24) │ │ │ │ +B92C3 Comment Length 0000 (0) │ │ │ │ +B92C5 Disk Start 0000 (0) │ │ │ │ +B92C7 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B92C9 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B92CD Local Header Offset 0001B91F (112927) │ │ │ │ +B92D1 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB92D1: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B92E5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B92E7 Length 0005 (5) │ │ │ │ +B92E9 Flags 01 (1) 'Modification' │ │ │ │ +B92EA Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B92EE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B92F0 Length 000B (11) │ │ │ │ +B92F2 Version 01 (1) │ │ │ │ +B92F3 UID Size 04 (4) │ │ │ │ +B92F4 UID 00000000 (0) │ │ │ │ +B92F8 GID Size 04 (4) │ │ │ │ +B92F9 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B92FD CENTRAL HEADER #15 02014B50 (33639248) │ │ │ │ +B9301 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9302 Created OS 03 (3) 'Unix' │ │ │ │ +B9303 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9304 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9305 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9307 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9309 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B930D CRC 9D665911 (2640730385) │ │ │ │ +B9311 Compressed Size 00001219 (4633) │ │ │ │ +B9315 Uncompressed Size 00003C91 (15505) │ │ │ │ +B9319 Filename Length 0010 (16) │ │ │ │ +B931B Extra Length 0018 (24) │ │ │ │ +B931D Comment Length 0000 (0) │ │ │ │ +B931F Disk Start 0000 (0) │ │ │ │ +B9321 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9323 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9327 Local Header Offset 00024B0E (150286) │ │ │ │ +B932B Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB932B: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B933B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B933D Length 0005 (5) │ │ │ │ +B933F Flags 01 (1) 'Modification' │ │ │ │ +B9340 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9344 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9346 Length 000B (11) │ │ │ │ +B9348 Version 01 (1) │ │ │ │ +B9349 UID Size 04 (4) │ │ │ │ +B934A UID 00000000 (0) │ │ │ │ +B934E GID Size 04 (4) │ │ │ │ +B934F GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9353 CENTRAL HEADER #16 02014B50 (33639248) │ │ │ │ +B9357 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9358 Created OS 03 (3) 'Unix' │ │ │ │ +B9359 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B935A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B935B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B935D Compression Method 0008 (8) 'Deflated' │ │ │ │ +B935F Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9363 CRC 56720776 (1450313590) │ │ │ │ +B9367 Compressed Size 00002A63 (10851) │ │ │ │ +B936B Uncompressed Size 0001151F (70943) │ │ │ │ +B936F Filename Length 0016 (22) │ │ │ │ +B9371 Extra Length 0018 (24) │ │ │ │ +B9373 Comment Length 0000 (0) │ │ │ │ +B9375 Disk Start 0000 (0) │ │ │ │ +B9377 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9379 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B937D Local Header Offset 00025D71 (154993) │ │ │ │ +B9381 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9381: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9397 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9399 Length 0005 (5) │ │ │ │ +B939B Flags 01 (1) 'Modification' │ │ │ │ +B939C Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B93A0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B93A2 Length 000B (11) │ │ │ │ +B93A4 Version 01 (1) │ │ │ │ +B93A5 UID Size 04 (4) │ │ │ │ +B93A6 UID 00000000 (0) │ │ │ │ +B93AA GID Size 04 (4) │ │ │ │ +B93AB GID 00000000 (0) │ │ │ │ + │ │ │ │ +B93AF CENTRAL HEADER #17 02014B50 (33639248) │ │ │ │ +B93B3 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B93B4 Created OS 03 (3) 'Unix' │ │ │ │ +B93B5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B93B6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B93B7 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B93B9 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B93BB Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B93BF CRC 8CD1C33D (2362557245) │ │ │ │ +B93C3 Compressed Size 000014DA (5338) │ │ │ │ +B93C7 Uncompressed Size 0000518D (20877) │ │ │ │ +B93CB Filename Length 001D (29) │ │ │ │ +B93CD Extra Length 0018 (24) │ │ │ │ +B93CF Comment Length 0000 (0) │ │ │ │ +B93D1 Disk Start 0000 (0) │ │ │ │ +B93D3 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B93D5 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B93D9 Local Header Offset 00028824 (165924) │ │ │ │ +B93DD Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB93DD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B93FA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B93FC Length 0005 (5) │ │ │ │ +B93FE Flags 01 (1) 'Modification' │ │ │ │ +B93FF Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9403 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9405 Length 000B (11) │ │ │ │ +B9407 Version 01 (1) │ │ │ │ +B9408 UID Size 04 (4) │ │ │ │ +B9409 UID 00000000 (0) │ │ │ │ +B940D GID Size 04 (4) │ │ │ │ +B940E GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9412 CENTRAL HEADER #18 02014B50 (33639248) │ │ │ │ +B9416 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9417 Created OS 03 (3) 'Unix' │ │ │ │ +B9418 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9419 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B941A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B941C Compression Method 0008 (8) 'Deflated' │ │ │ │ +B941E Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9422 CRC AD8C2B6A (2911644522) │ │ │ │ +B9426 Compressed Size 0000380C (14348) │ │ │ │ +B942A Uncompressed Size 0000EA4C (59980) │ │ │ │ +B942E Filename Length 001C (28) │ │ │ │ +B9430 Extra Length 0018 (24) │ │ │ │ +B9432 Comment Length 0000 (0) │ │ │ │ +B9434 Disk Start 0000 (0) │ │ │ │ +B9436 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9438 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B943C Local Header Offset 00029D55 (171349) │ │ │ │ +B9440 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9440: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B945C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B945E Length 0005 (5) │ │ │ │ +B9460 Flags 01 (1) 'Modification' │ │ │ │ +B9461 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9465 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9467 Length 000B (11) │ │ │ │ +B9469 Version 01 (1) │ │ │ │ +B946A UID Size 04 (4) │ │ │ │ +B946B UID 00000000 (0) │ │ │ │ +B946F GID Size 04 (4) │ │ │ │ +B9470 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9474 CENTRAL HEADER #19 02014B50 (33639248) │ │ │ │ +B9478 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9479 Created OS 03 (3) 'Unix' │ │ │ │ +B947A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B947B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B947C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B947E Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9480 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9484 CRC 2E09C12B (772391211) │ │ │ │ +B9488 Compressed Size 000006A2 (1698) │ │ │ │ +B948C Uncompressed Size 000011F4 (4596) │ │ │ │ +B9490 Filename Length 001C (28) │ │ │ │ +B9492 Extra Length 0018 (24) │ │ │ │ +B9494 Comment Length 0000 (0) │ │ │ │ +B9496 Disk Start 0000 (0) │ │ │ │ +B9498 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B949A Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B949E Local Header Offset 0002D5B7 (185783) │ │ │ │ +B94A2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB94A2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B94BE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B94C0 Length 0005 (5) │ │ │ │ +B94C2 Flags 01 (1) 'Modification' │ │ │ │ +B94C3 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B94C7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B94C9 Length 000B (11) │ │ │ │ +B94CB Version 01 (1) │ │ │ │ +B94CC UID Size 04 (4) │ │ │ │ +B94CD UID 00000000 (0) │ │ │ │ +B94D1 GID Size 04 (4) │ │ │ │ +B94D2 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B94D6 CENTRAL HEADER #20 02014B50 (33639248) │ │ │ │ +B94DA Created Zip Spec 3D (61) '6.1' │ │ │ │ +B94DB Created OS 03 (3) 'Unix' │ │ │ │ +B94DC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B94DD Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B94DE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B94E0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B94E2 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B94E6 CRC BFC83E05 (3217571333) │ │ │ │ +B94EA Compressed Size 00001082 (4226) │ │ │ │ +B94EE Uncompressed Size 00004BFF (19455) │ │ │ │ +B94F2 Filename Length 001B (27) │ │ │ │ +B94F4 Extra Length 0018 (24) │ │ │ │ +B94F6 Comment Length 0000 (0) │ │ │ │ +B94F8 Disk Start 0000 (0) │ │ │ │ +B94FA Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B94FC Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9500 Local Header Offset 0002DCAF (187567) │ │ │ │ +B9504 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9504: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B951F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9521 Length 0005 (5) │ │ │ │ +B9523 Flags 01 (1) 'Modification' │ │ │ │ +B9524 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9528 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B952A Length 000B (11) │ │ │ │ +B952C Version 01 (1) │ │ │ │ +B952D UID Size 04 (4) │ │ │ │ +B952E UID 00000000 (0) │ │ │ │ +B9532 GID Size 04 (4) │ │ │ │ +B9533 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9537 CENTRAL HEADER #21 02014B50 (33639248) │ │ │ │ +B953B Created Zip Spec 3D (61) '6.1' │ │ │ │ +B953C Created OS 03 (3) 'Unix' │ │ │ │ +B953D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B953E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B953F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9541 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9543 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9547 CRC 5F7510E3 (1601507555) │ │ │ │ +B954B Compressed Size 00003BDB (15323) │ │ │ │ +B954F Uncompressed Size 0000D783 (55171) │ │ │ │ +B9553 Filename Length 001D (29) │ │ │ │ +B9555 Extra Length 0018 (24) │ │ │ │ +B9557 Comment Length 0000 (0) │ │ │ │ +B9559 Disk Start 0000 (0) │ │ │ │ +B955B Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B955D Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9561 Local Header Offset 0002ED86 (191878) │ │ │ │ +B9565 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9565: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9582 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9584 Length 0005 (5) │ │ │ │ +B9586 Flags 01 (1) 'Modification' │ │ │ │ +B9587 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B958B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B958D Length 000B (11) │ │ │ │ +B958F Version 01 (1) │ │ │ │ +B9590 UID Size 04 (4) │ │ │ │ +B9591 UID 00000000 (0) │ │ │ │ +B9595 GID Size 04 (4) │ │ │ │ +B9596 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B959A CENTRAL HEADER #22 02014B50 (33639248) │ │ │ │ +B959E Created Zip Spec 3D (61) '6.1' │ │ │ │ +B959F Created OS 03 (3) 'Unix' │ │ │ │ +B95A0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B95A1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B95A2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B95A4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B95A6 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B95AA CRC 8217C9B8 (2182597048) │ │ │ │ +B95AE Compressed Size 00000D6B (3435) │ │ │ │ +B95B2 Uncompressed Size 0000388D (14477) │ │ │ │ +B95B6 Filename Length 001D (29) │ │ │ │ +B95B8 Extra Length 0018 (24) │ │ │ │ +B95BA Comment Length 0000 (0) │ │ │ │ +B95BC Disk Start 0000 (0) │ │ │ │ +B95BE Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B95C0 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B95C4 Local Header Offset 000329B8 (207288) │ │ │ │ +B95C8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB95C8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B95E5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B95E7 Length 0005 (5) │ │ │ │ +B95E9 Flags 01 (1) 'Modification' │ │ │ │ +B95EA Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B95EE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B95F0 Length 000B (11) │ │ │ │ +B95F2 Version 01 (1) │ │ │ │ +B95F3 UID Size 04 (4) │ │ │ │ +B95F4 UID 00000000 (0) │ │ │ │ +B95F8 GID Size 04 (4) │ │ │ │ +B95F9 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B95FD CENTRAL HEADER #23 02014B50 (33639248) │ │ │ │ +B9601 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9602 Created OS 03 (3) 'Unix' │ │ │ │ +B9603 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9604 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9605 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9607 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9609 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B960D CRC AD4FDB1F (2907691807) │ │ │ │ +B9611 Compressed Size 00001C69 (7273) │ │ │ │ +B9615 Uncompressed Size 0000C186 (49542) │ │ │ │ +B9619 Filename Length 001A (26) │ │ │ │ +B961B Extra Length 0018 (24) │ │ │ │ +B961D Comment Length 0000 (0) │ │ │ │ +B961F Disk Start 0000 (0) │ │ │ │ +B9621 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9623 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9627 Local Header Offset 0003377A (210810) │ │ │ │ +B962B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB962B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9645 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9647 Length 0005 (5) │ │ │ │ +B9649 Flags 01 (1) 'Modification' │ │ │ │ +B964A Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B964E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9650 Length 000B (11) │ │ │ │ +B9652 Version 01 (1) │ │ │ │ +B9653 UID Size 04 (4) │ │ │ │ +B9654 UID 00000000 (0) │ │ │ │ +B9658 GID Size 04 (4) │ │ │ │ +B9659 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B965D CENTRAL HEADER #24 02014B50 (33639248) │ │ │ │ +B9661 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9662 Created OS 03 (3) 'Unix' │ │ │ │ +B9663 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9664 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9665 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9667 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9669 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B966D CRC 04F1FDC5 (82968005) │ │ │ │ +B9671 Compressed Size 000003DF (991) │ │ │ │ +B9675 Uncompressed Size 00000935 (2357) │ │ │ │ +B9679 Filename Length 0012 (18) │ │ │ │ +B967B Extra Length 0018 (24) │ │ │ │ +B967D Comment Length 0000 (0) │ │ │ │ +B967F Disk Start 0000 (0) │ │ │ │ +B9681 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9683 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9687 Local Header Offset 00035437 (218167) │ │ │ │ +B968B Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB968B: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B969D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B969F Length 0005 (5) │ │ │ │ +B96A1 Flags 01 (1) 'Modification' │ │ │ │ +B96A2 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B96A6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B96A8 Length 000B (11) │ │ │ │ +B96AA Version 01 (1) │ │ │ │ +B96AB UID Size 04 (4) │ │ │ │ +B96AC UID 00000000 (0) │ │ │ │ +B96B0 GID Size 04 (4) │ │ │ │ +B96B1 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B96B5 CENTRAL HEADER #25 02014B50 (33639248) │ │ │ │ +B96B9 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B96BA Created OS 03 (3) 'Unix' │ │ │ │ +B96BB Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B96BC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B96BD General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B96BF Compression Method 0008 (8) 'Deflated' │ │ │ │ +B96C1 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B96C5 CRC C605FB96 (3322280854) │ │ │ │ +B96C9 Compressed Size 000001D3 (467) │ │ │ │ +B96CD Uncompressed Size 00000311 (785) │ │ │ │ +B96D1 Filename Length 0020 (32) │ │ │ │ +B96D3 Extra Length 0018 (24) │ │ │ │ +B96D5 Comment Length 0000 (0) │ │ │ │ +B96D7 Disk Start 0000 (0) │ │ │ │ +B96D9 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B96DB Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B96DF Local Header Offset 00035862 (219234) │ │ │ │ +B96E3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB96E3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9703 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9705 Length 0005 (5) │ │ │ │ +B9707 Flags 01 (1) 'Modification' │ │ │ │ +B9708 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B970C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B970E Length 000B (11) │ │ │ │ +B9710 Version 01 (1) │ │ │ │ +B9711 UID Size 04 (4) │ │ │ │ +B9712 UID 00000000 (0) │ │ │ │ +B9716 GID Size 04 (4) │ │ │ │ +B9717 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B971B CENTRAL HEADER #26 02014B50 (33639248) │ │ │ │ +B971F Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9720 Created OS 03 (3) 'Unix' │ │ │ │ +B9721 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9722 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9723 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9725 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9727 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B972B CRC BBDF5426 (3151975462) │ │ │ │ +B972F Compressed Size 000017AB (6059) │ │ │ │ +B9733 Uncompressed Size 00009D18 (40216) │ │ │ │ +B9737 Filename Length 001B (27) │ │ │ │ +B9739 Extra Length 0018 (24) │ │ │ │ +B973B Comment Length 0000 (0) │ │ │ │ +B973D Disk Start 0000 (0) │ │ │ │ +B973F Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9741 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9745 Local Header Offset 00035A8F (219791) │ │ │ │ +B9749 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9749: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9764 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9766 Length 0005 (5) │ │ │ │ +B9768 Flags 01 (1) 'Modification' │ │ │ │ +B9769 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B976D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B976F Length 000B (11) │ │ │ │ +B9771 Version 01 (1) │ │ │ │ +B9772 UID Size 04 (4) │ │ │ │ +B9773 UID 00000000 (0) │ │ │ │ +B9777 GID Size 04 (4) │ │ │ │ +B9778 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B977C CENTRAL HEADER #27 02014B50 (33639248) │ │ │ │ +B9780 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9781 Created OS 03 (3) 'Unix' │ │ │ │ +B9782 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9783 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9784 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9786 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9788 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B978C CRC D6E9E108 (3605651720) │ │ │ │ +B9790 Compressed Size 00001371 (4977) │ │ │ │ +B9794 Uncompressed Size 00003B61 (15201) │ │ │ │ +B9798 Filename Length 0015 (21) │ │ │ │ +B979A Extra Length 0018 (24) │ │ │ │ +B979C Comment Length 0000 (0) │ │ │ │ +B979E Disk Start 0000 (0) │ │ │ │ +B97A0 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B97A2 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B97A6 Local Header Offset 0003728F (225935) │ │ │ │ +B97AA Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB97AA: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B97BF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B97C1 Length 0005 (5) │ │ │ │ +B97C3 Flags 01 (1) 'Modification' │ │ │ │ +B97C4 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B97C8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B97CA Length 000B (11) │ │ │ │ +B97CC Version 01 (1) │ │ │ │ +B97CD UID Size 04 (4) │ │ │ │ +B97CE UID 00000000 (0) │ │ │ │ +B97D2 GID Size 04 (4) │ │ │ │ +B97D3 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B97D7 CENTRAL HEADER #28 02014B50 (33639248) │ │ │ │ +B97DB Created Zip Spec 3D (61) '6.1' │ │ │ │ +B97DC Created OS 03 (3) 'Unix' │ │ │ │ +B97DD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B97DE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B97DF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B97E1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B97E3 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B97E7 CRC AD15BDB2 (2903883186) │ │ │ │ +B97EB Compressed Size 00000AD0 (2768) │ │ │ │ +B97EF Uncompressed Size 00002135 (8501) │ │ │ │ +B97F3 Filename Length 0011 (17) │ │ │ │ +B97F5 Extra Length 0018 (24) │ │ │ │ +B97F7 Comment Length 0000 (0) │ │ │ │ +B97F9 Disk Start 0000 (0) │ │ │ │ +B97FB Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B97FD Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9801 Local Header Offset 0003864F (230991) │ │ │ │ +B9805 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9805: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9816 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9818 Length 0005 (5) │ │ │ │ +B981A Flags 01 (1) 'Modification' │ │ │ │ +B981B Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B981F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9821 Length 000B (11) │ │ │ │ +B9823 Version 01 (1) │ │ │ │ +B9824 UID Size 04 (4) │ │ │ │ +B9825 UID 00000000 (0) │ │ │ │ +B9829 GID Size 04 (4) │ │ │ │ +B982A GID 00000000 (0) │ │ │ │ + │ │ │ │ +B982E CENTRAL HEADER #29 02014B50 (33639248) │ │ │ │ +B9832 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9833 Created OS 03 (3) 'Unix' │ │ │ │ +B9834 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9835 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9836 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9838 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B983A Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B983E CRC 30C3E93A (818145594) │ │ │ │ +B9842 Compressed Size 000003FE (1022) │ │ │ │ +B9846 Uncompressed Size 00000F0C (3852) │ │ │ │ +B984A Filename Length 0014 (20) │ │ │ │ +B984C Extra Length 0018 (24) │ │ │ │ +B984E Comment Length 0000 (0) │ │ │ │ +B9850 Disk Start 0000 (0) │ │ │ │ +B9852 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9854 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9858 Local Header Offset 0003916A (233834) │ │ │ │ +B985C Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB985C: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9870 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9872 Length 0005 (5) │ │ │ │ +B9874 Flags 01 (1) 'Modification' │ │ │ │ +B9875 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9879 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B987B Length 000B (11) │ │ │ │ +B987D Version 01 (1) │ │ │ │ +B987E UID Size 04 (4) │ │ │ │ +B987F UID 00000000 (0) │ │ │ │ +B9883 GID Size 04 (4) │ │ │ │ +B9884 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9888 CENTRAL HEADER #30 02014B50 (33639248) │ │ │ │ +B988C Created Zip Spec 3D (61) '6.1' │ │ │ │ +B988D Created OS 03 (3) 'Unix' │ │ │ │ +B988E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B988F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9890 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9892 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9894 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9898 CRC F6397314 (4130960148) │ │ │ │ +B989C Compressed Size 00001260 (4704) │ │ │ │ +B98A0 Uncompressed Size 0000346B (13419) │ │ │ │ +B98A4 Filename Length 0014 (20) │ │ │ │ +B98A6 Extra Length 0018 (24) │ │ │ │ +B98A8 Comment Length 0000 (0) │ │ │ │ +B98AA Disk Start 0000 (0) │ │ │ │ +B98AC Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B98AE Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B98B2 Local Header Offset 000395B6 (234934) │ │ │ │ +B98B6 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB98B6: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B98CA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B98CC Length 0005 (5) │ │ │ │ +B98CE Flags 01 (1) 'Modification' │ │ │ │ +B98CF Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B98D3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B98D5 Length 000B (11) │ │ │ │ +B98D7 Version 01 (1) │ │ │ │ +B98D8 UID Size 04 (4) │ │ │ │ +B98D9 UID 00000000 (0) │ │ │ │ +B98DD GID Size 04 (4) │ │ │ │ +B98DE GID 00000000 (0) │ │ │ │ + │ │ │ │ +B98E2 CENTRAL HEADER #31 02014B50 (33639248) │ │ │ │ +B98E6 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B98E7 Created OS 03 (3) 'Unix' │ │ │ │ +B98E8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B98E9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B98EA General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B98EC Compression Method 0008 (8) 'Deflated' │ │ │ │ +B98EE Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B98F2 CRC F699BBFB (4137270267) │ │ │ │ +B98F6 Compressed Size 00000ACF (2767) │ │ │ │ +B98FA Uncompressed Size 000022FF (8959) │ │ │ │ +B98FE Filename Length 001B (27) │ │ │ │ +B9900 Extra Length 0018 (24) │ │ │ │ +B9902 Comment Length 0000 (0) │ │ │ │ +B9904 Disk Start 0000 (0) │ │ │ │ +B9906 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9908 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B990C Local Header Offset 0003A864 (239716) │ │ │ │ +B9910 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9910: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B992B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B992D Length 0005 (5) │ │ │ │ +B992F Flags 01 (1) 'Modification' │ │ │ │ +B9930 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9934 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9936 Length 000B (11) │ │ │ │ +B9938 Version 01 (1) │ │ │ │ +B9939 UID Size 04 (4) │ │ │ │ +B993A UID 00000000 (0) │ │ │ │ +B993E GID Size 04 (4) │ │ │ │ +B993F GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9943 CENTRAL HEADER #32 02014B50 (33639248) │ │ │ │ +B9947 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9948 Created OS 03 (3) 'Unix' │ │ │ │ +B9949 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B994A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B994B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B994D Compression Method 0008 (8) 'Deflated' │ │ │ │ +B994F Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9953 CRC C8DC1D85 (3369868677) │ │ │ │ +B9957 Compressed Size 00000C51 (3153) │ │ │ │ +B995B Uncompressed Size 0000273C (10044) │ │ │ │ +B995F Filename Length 0013 (19) │ │ │ │ +B9961 Extra Length 0018 (24) │ │ │ │ +B9963 Comment Length 0000 (0) │ │ │ │ +B9965 Disk Start 0000 (0) │ │ │ │ +B9967 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9969 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B996D Local Header Offset 0003B388 (242568) │ │ │ │ +B9971 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9971: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9984 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9986 Length 0005 (5) │ │ │ │ +B9988 Flags 01 (1) 'Modification' │ │ │ │ +B9989 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B998D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B998F Length 000B (11) │ │ │ │ +B9991 Version 01 (1) │ │ │ │ +B9992 UID Size 04 (4) │ │ │ │ +B9993 UID 00000000 (0) │ │ │ │ +B9997 GID Size 04 (4) │ │ │ │ +B9998 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B999C CENTRAL HEADER #33 02014B50 (33639248) │ │ │ │ +B99A0 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B99A1 Created OS 03 (3) 'Unix' │ │ │ │ +B99A2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B99A3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B99A4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B99A6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B99A8 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B99AC CRC 3E3BCF44 (1044107076) │ │ │ │ +B99B0 Compressed Size 00000C92 (3218) │ │ │ │ +B99B4 Uncompressed Size 00003D10 (15632) │ │ │ │ +B99B8 Filename Length 0014 (20) │ │ │ │ +B99BA Extra Length 0018 (24) │ │ │ │ +B99BC Comment Length 0000 (0) │ │ │ │ +B99BE Disk Start 0000 (0) │ │ │ │ +B99C0 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B99C2 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B99C6 Local Header Offset 0003C026 (245798) │ │ │ │ +B99CA Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB99CA: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B99DE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B99E0 Length 0005 (5) │ │ │ │ +B99E2 Flags 01 (1) 'Modification' │ │ │ │ +B99E3 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B99E7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B99E9 Length 000B (11) │ │ │ │ +B99EB Version 01 (1) │ │ │ │ +B99EC UID Size 04 (4) │ │ │ │ +B99ED UID 00000000 (0) │ │ │ │ +B99F1 GID Size 04 (4) │ │ │ │ +B99F2 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B99F6 CENTRAL HEADER #34 02014B50 (33639248) │ │ │ │ +B99FA Created Zip Spec 3D (61) '6.1' │ │ │ │ +B99FB Created OS 03 (3) 'Unix' │ │ │ │ +B99FC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B99FD Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B99FE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9A00 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9A02 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9A06 CRC 6812E40F (1746068495) │ │ │ │ +B9A0A Compressed Size 00000F45 (3909) │ │ │ │ +B9A0E Uncompressed Size 00003744 (14148) │ │ │ │ +B9A12 Filename Length 000F (15) │ │ │ │ +B9A14 Extra Length 0018 (24) │ │ │ │ +B9A16 Comment Length 0000 (0) │ │ │ │ +B9A18 Disk Start 0000 (0) │ │ │ │ +B9A1A Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9A1C Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9A20 Local Header Offset 0003CD06 (249094) │ │ │ │ +B9A24 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9A24: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9A33 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9A35 Length 0005 (5) │ │ │ │ +B9A37 Flags 01 (1) 'Modification' │ │ │ │ +B9A38 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9A3C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9A3E Length 000B (11) │ │ │ │ +B9A40 Version 01 (1) │ │ │ │ +B9A41 UID Size 04 (4) │ │ │ │ +B9A42 UID 00000000 (0) │ │ │ │ +B9A46 GID Size 04 (4) │ │ │ │ +B9A47 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9A4B CENTRAL HEADER #35 02014B50 (33639248) │ │ │ │ +B9A4F Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9A50 Created OS 03 (3) 'Unix' │ │ │ │ +B9A51 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9A52 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9A53 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9A55 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9A57 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9A5B CRC 1FC86902 (533227778) │ │ │ │ +B9A5F Compressed Size 000006BA (1722) │ │ │ │ +B9A63 Uncompressed Size 00001A79 (6777) │ │ │ │ +B9A67 Filename Length 000F (15) │ │ │ │ +B9A69 Extra Length 0018 (24) │ │ │ │ +B9A6B Comment Length 0000 (0) │ │ │ │ +B9A6D Disk Start 0000 (0) │ │ │ │ +B9A6F Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9A71 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9A75 Local Header Offset 0003DC94 (253076) │ │ │ │ +B9A79 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9A79: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9A88 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9A8A Length 0005 (5) │ │ │ │ +B9A8C Flags 01 (1) 'Modification' │ │ │ │ +B9A8D Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9A91 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9A93 Length 000B (11) │ │ │ │ +B9A95 Version 01 (1) │ │ │ │ +B9A96 UID Size 04 (4) │ │ │ │ +B9A97 UID 00000000 (0) │ │ │ │ +B9A9B GID Size 04 (4) │ │ │ │ +B9A9C GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9AA0 CENTRAL HEADER #36 02014B50 (33639248) │ │ │ │ +B9AA4 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9AA5 Created OS 03 (3) 'Unix' │ │ │ │ +B9AA6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9AA7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9AA8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9AAA Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9AAC Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9AB0 CRC D3195915 (3541653781) │ │ │ │ +B9AB4 Compressed Size 00001A45 (6725) │ │ │ │ +B9AB8 Uncompressed Size 000064FA (25850) │ │ │ │ +B9ABC Filename Length 0013 (19) │ │ │ │ +B9ABE Extra Length 0018 (24) │ │ │ │ +B9AC0 Comment Length 0000 (0) │ │ │ │ +B9AC2 Disk Start 0000 (0) │ │ │ │ +B9AC4 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9AC6 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9ACA Local Header Offset 0003E397 (254871) │ │ │ │ +B9ACE Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9ACE: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9AE1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9AE3 Length 0005 (5) │ │ │ │ +B9AE5 Flags 01 (1) 'Modification' │ │ │ │ +B9AE6 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9AEA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9AEC Length 000B (11) │ │ │ │ +B9AEE Version 01 (1) │ │ │ │ +B9AEF UID Size 04 (4) │ │ │ │ +B9AF0 UID 00000000 (0) │ │ │ │ +B9AF4 GID Size 04 (4) │ │ │ │ +B9AF5 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9AF9 CENTRAL HEADER #37 02014B50 (33639248) │ │ │ │ +B9AFD Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9AFE Created OS 03 (3) 'Unix' │ │ │ │ +B9AFF Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9B00 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9B01 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9B03 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9B05 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9B09 CRC 8BADFA46 (2343434822) │ │ │ │ +B9B0D Compressed Size 000009A5 (2469) │ │ │ │ +B9B11 Uncompressed Size 00001B64 (7012) │ │ │ │ +B9B15 Filename Length 0010 (16) │ │ │ │ +B9B17 Extra Length 0018 (24) │ │ │ │ +B9B19 Comment Length 0000 (0) │ │ │ │ +B9B1B Disk Start 0000 (0) │ │ │ │ +B9B1D Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9B1F Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9B23 Local Header Offset 0003FE29 (261673) │ │ │ │ +B9B27 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9B27: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9B37 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9B39 Length 0005 (5) │ │ │ │ +B9B3B Flags 01 (1) 'Modification' │ │ │ │ +B9B3C Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9B40 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9B42 Length 000B (11) │ │ │ │ +B9B44 Version 01 (1) │ │ │ │ +B9B45 UID Size 04 (4) │ │ │ │ +B9B46 UID 00000000 (0) │ │ │ │ +B9B4A GID Size 04 (4) │ │ │ │ +B9B4B GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9B4F CENTRAL HEADER #38 02014B50 (33639248) │ │ │ │ +B9B53 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9B54 Created OS 03 (3) 'Unix' │ │ │ │ +B9B55 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9B56 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9B57 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9B59 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9B5B Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9B5F CRC 5E895E09 (1586060809) │ │ │ │ +B9B63 Compressed Size 000006B6 (1718) │ │ │ │ +B9B67 Uncompressed Size 00001565 (5477) │ │ │ │ +B9B6B Filename Length 0012 (18) │ │ │ │ +B9B6D Extra Length 0018 (24) │ │ │ │ +B9B6F Comment Length 0000 (0) │ │ │ │ +B9B71 Disk Start 0000 (0) │ │ │ │ +B9B73 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9B75 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9B79 Local Header Offset 00040818 (264216) │ │ │ │ +B9B7D Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9B7D: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9B8F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9B91 Length 0005 (5) │ │ │ │ +B9B93 Flags 01 (1) 'Modification' │ │ │ │ +B9B94 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9B98 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9B9A Length 000B (11) │ │ │ │ +B9B9C Version 01 (1) │ │ │ │ +B9B9D UID Size 04 (4) │ │ │ │ +B9B9E UID 00000000 (0) │ │ │ │ +B9BA2 GID Size 04 (4) │ │ │ │ +B9BA3 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9BA7 CENTRAL HEADER #39 02014B50 (33639248) │ │ │ │ +B9BAB Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9BAC Created OS 03 (3) 'Unix' │ │ │ │ +B9BAD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9BAE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9BAF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9BB1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9BB3 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9BB7 CRC C45D7864 (3294460004) │ │ │ │ +B9BBB Compressed Size 00002D5E (11614) │ │ │ │ +B9BBF Uncompressed Size 0000D07E (53374) │ │ │ │ +B9BC3 Filename Length 0010 (16) │ │ │ │ +B9BC5 Extra Length 0018 (24) │ │ │ │ +B9BC7 Comment Length 0000 (0) │ │ │ │ +B9BC9 Disk Start 0000 (0) │ │ │ │ +B9BCB Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9BCD Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9BD1 Local Header Offset 00040F1A (266010) │ │ │ │ +B9BD5 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9BD5: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9BE5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9BE7 Length 0005 (5) │ │ │ │ +B9BE9 Flags 01 (1) 'Modification' │ │ │ │ +B9BEA Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9BEE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9BF0 Length 000B (11) │ │ │ │ +B9BF2 Version 01 (1) │ │ │ │ +B9BF3 UID Size 04 (4) │ │ │ │ +B9BF4 UID 00000000 (0) │ │ │ │ +B9BF8 GID Size 04 (4) │ │ │ │ +B9BF9 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9BFD CENTRAL HEADER #40 02014B50 (33639248) │ │ │ │ +B9C01 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9C02 Created OS 03 (3) 'Unix' │ │ │ │ +B9C03 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9C04 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9C05 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9C07 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9C09 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9C0D CRC 8F7E3CC4 (2407414980) │ │ │ │ +B9C11 Compressed Size 00001E83 (7811) │ │ │ │ +B9C15 Uncompressed Size 00009AAA (39594) │ │ │ │ +B9C19 Filename Length 0012 (18) │ │ │ │ +B9C1B Extra Length 0018 (24) │ │ │ │ +B9C1D Comment Length 0000 (0) │ │ │ │ +B9C1F Disk Start 0000 (0) │ │ │ │ +B9C21 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9C23 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9C27 Local Header Offset 00043CC2 (277698) │ │ │ │ +B9C2B Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9C2B: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9C3D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9C3F Length 0005 (5) │ │ │ │ +B9C41 Flags 01 (1) 'Modification' │ │ │ │ +B9C42 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9C46 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9C48 Length 000B (11) │ │ │ │ +B9C4A Version 01 (1) │ │ │ │ +B9C4B UID Size 04 (4) │ │ │ │ +B9C4C UID 00000000 (0) │ │ │ │ +B9C50 GID Size 04 (4) │ │ │ │ +B9C51 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9C55 CENTRAL HEADER #41 02014B50 (33639248) │ │ │ │ +B9C59 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9C5A Created OS 03 (3) 'Unix' │ │ │ │ +B9C5B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9C5C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9C5D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9C5F Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9C61 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9C65 CRC 77D28BC2 (2010287042) │ │ │ │ +B9C69 Compressed Size 0000147B (5243) │ │ │ │ +B9C6D Uncompressed Size 00007ACF (31439) │ │ │ │ +B9C71 Filename Length 0018 (24) │ │ │ │ +B9C73 Extra Length 0018 (24) │ │ │ │ +B9C75 Comment Length 0000 (0) │ │ │ │ +B9C77 Disk Start 0000 (0) │ │ │ │ +B9C79 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9C7B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9C7F Local Header Offset 00045B91 (285585) │ │ │ │ +B9C83 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9C83: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9C9B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9C9D Length 0005 (5) │ │ │ │ +B9C9F Flags 01 (1) 'Modification' │ │ │ │ +B9CA0 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9CA4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9CA6 Length 000B (11) │ │ │ │ +B9CA8 Version 01 (1) │ │ │ │ +B9CA9 UID Size 04 (4) │ │ │ │ +B9CAA UID 00000000 (0) │ │ │ │ +B9CAE GID Size 04 (4) │ │ │ │ +B9CAF GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9CB3 CENTRAL HEADER #42 02014B50 (33639248) │ │ │ │ +B9CB7 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9CB8 Created OS 03 (3) 'Unix' │ │ │ │ +B9CB9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9CBA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9CBB General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9CBD Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9CBF Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9CC3 CRC C14122F3 (3242271475) │ │ │ │ +B9CC7 Compressed Size 000021E0 (8672) │ │ │ │ +B9CCB Uncompressed Size 0000D220 (53792) │ │ │ │ +B9CCF Filename Length 001F (31) │ │ │ │ +B9CD1 Extra Length 0018 (24) │ │ │ │ +B9CD3 Comment Length 0000 (0) │ │ │ │ +B9CD5 Disk Start 0000 (0) │ │ │ │ +B9CD7 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9CD9 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9CDD Local Header Offset 0004705E (290910) │ │ │ │ +B9CE1 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9CE1: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9D00 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9D02 Length 0005 (5) │ │ │ │ +B9D04 Flags 01 (1) 'Modification' │ │ │ │ +B9D05 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9D09 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9D0B Length 000B (11) │ │ │ │ +B9D0D Version 01 (1) │ │ │ │ +B9D0E UID Size 04 (4) │ │ │ │ +B9D0F UID 00000000 (0) │ │ │ │ +B9D13 GID Size 04 (4) │ │ │ │ +B9D14 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9D18 CENTRAL HEADER #43 02014B50 (33639248) │ │ │ │ +B9D1C Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9D1D Created OS 03 (3) 'Unix' │ │ │ │ +B9D1E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9D1F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9D20 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9D22 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9D24 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9D28 CRC E6EB8E0C (3874197004) │ │ │ │ +B9D2C Compressed Size 000003F7 (1015) │ │ │ │ +B9D30 Uncompressed Size 000008A3 (2211) │ │ │ │ +B9D34 Filename Length 001E (30) │ │ │ │ +B9D36 Extra Length 0018 (24) │ │ │ │ +B9D38 Comment Length 0000 (0) │ │ │ │ +B9D3A Disk Start 0000 (0) │ │ │ │ +B9D3C Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9D3E Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9D42 Local Header Offset 00049297 (299671) │ │ │ │ +B9D46 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9D46: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9D64 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9D66 Length 0005 (5) │ │ │ │ +B9D68 Flags 01 (1) 'Modification' │ │ │ │ +B9D69 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9D6D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9D6F Length 000B (11) │ │ │ │ +B9D71 Version 01 (1) │ │ │ │ +B9D72 UID Size 04 (4) │ │ │ │ +B9D73 UID 00000000 (0) │ │ │ │ +B9D77 GID Size 04 (4) │ │ │ │ +B9D78 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9D7C CENTRAL HEADER #44 02014B50 (33639248) │ │ │ │ +B9D80 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9D81 Created OS 03 (3) 'Unix' │ │ │ │ +B9D82 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9D83 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9D84 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9D86 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9D88 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9D8C CRC 352C2657 (892085847) │ │ │ │ +B9D90 Compressed Size 000042FE (17150) │ │ │ │ +B9D94 Uncompressed Size 0000DAE1 (56033) │ │ │ │ +B9D98 Filename Length 0013 (19) │ │ │ │ +B9D9A Extra Length 0018 (24) │ │ │ │ +B9D9C Comment Length 0000 (0) │ │ │ │ +B9D9E Disk Start 0000 (0) │ │ │ │ +B9DA0 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9DA2 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9DA6 Local Header Offset 000496E6 (300774) │ │ │ │ +B9DAA Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9DAA: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9DBD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9DBF Length 0005 (5) │ │ │ │ +B9DC1 Flags 01 (1) 'Modification' │ │ │ │ +B9DC2 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9DC6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9DC8 Length 000B (11) │ │ │ │ +B9DCA Version 01 (1) │ │ │ │ +B9DCB UID Size 04 (4) │ │ │ │ +B9DCC UID 00000000 (0) │ │ │ │ +B9DD0 GID Size 04 (4) │ │ │ │ +B9DD1 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9DD5 CENTRAL HEADER #45 02014B50 (33639248) │ │ │ │ +B9DD9 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9DDA Created OS 03 (3) 'Unix' │ │ │ │ +B9DDB Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9DDC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9DDD General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9DDF Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9DE1 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9DE5 CRC FCBDAAFD (4240288509) │ │ │ │ +B9DE9 Compressed Size 000026C3 (9923) │ │ │ │ +B9DED Uncompressed Size 00006E45 (28229) │ │ │ │ +B9DF1 Filename Length 0019 (25) │ │ │ │ +B9DF3 Extra Length 0018 (24) │ │ │ │ +B9DF5 Comment Length 0000 (0) │ │ │ │ +B9DF7 Disk Start 0000 (0) │ │ │ │ +B9DF9 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9DFB Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9DFF Local Header Offset 0004DA31 (318001) │ │ │ │ +B9E03 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9E03: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9E1C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9E1E Length 0005 (5) │ │ │ │ +B9E20 Flags 01 (1) 'Modification' │ │ │ │ +B9E21 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9E25 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9E27 Length 000B (11) │ │ │ │ +B9E29 Version 01 (1) │ │ │ │ +B9E2A UID Size 04 (4) │ │ │ │ +B9E2B UID 00000000 (0) │ │ │ │ +B9E2F GID Size 04 (4) │ │ │ │ +B9E30 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9E34 CENTRAL HEADER #46 02014B50 (33639248) │ │ │ │ +B9E38 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9E39 Created OS 03 (3) 'Unix' │ │ │ │ +B9E3A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9E3B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9E3C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9E3E Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9E40 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9E44 CRC BCB71496 (3166114966) │ │ │ │ +B9E48 Compressed Size 00002739 (10041) │ │ │ │ +B9E4C Uncompressed Size 00008B83 (35715) │ │ │ │ +B9E50 Filename Length 0019 (25) │ │ │ │ +B9E52 Extra Length 0018 (24) │ │ │ │ +B9E54 Comment Length 0000 (0) │ │ │ │ +B9E56 Disk Start 0000 (0) │ │ │ │ +B9E58 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9E5A Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9E5E Local Header Offset 00050147 (328007) │ │ │ │ +B9E62 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9E62: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9E7B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9E7D Length 0005 (5) │ │ │ │ +B9E7F Flags 01 (1) 'Modification' │ │ │ │ +B9E80 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9E84 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9E86 Length 000B (11) │ │ │ │ +B9E88 Version 01 (1) │ │ │ │ +B9E89 UID Size 04 (4) │ │ │ │ +B9E8A UID 00000000 (0) │ │ │ │ +B9E8E GID Size 04 (4) │ │ │ │ +B9E8F GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9E93 CENTRAL HEADER #47 02014B50 (33639248) │ │ │ │ +B9E97 Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9E98 Created OS 03 (3) 'Unix' │ │ │ │ +B9E99 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9E9A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9E9B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9E9D Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9E9F Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9EA3 CRC 69960DF9 (1771441657) │ │ │ │ +B9EA7 Compressed Size 00000ECD (3789) │ │ │ │ +B9EAB Uncompressed Size 000053BF (21439) │ │ │ │ +B9EAF Filename Length 0021 (33) │ │ │ │ +B9EB1 Extra Length 0018 (24) │ │ │ │ +B9EB3 Comment Length 0000 (0) │ │ │ │ +B9EB5 Disk Start 0000 (0) │ │ │ │ +B9EB7 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9EB9 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9EBD Local Header Offset 000528D3 (338131) │ │ │ │ +B9EC1 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9EC1: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9EE2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9EE4 Length 0005 (5) │ │ │ │ +B9EE6 Flags 01 (1) 'Modification' │ │ │ │ +B9EE7 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9EEB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9EED Length 000B (11) │ │ │ │ +B9EEF Version 01 (1) │ │ │ │ +B9EF0 UID Size 04 (4) │ │ │ │ +B9EF1 UID 00000000 (0) │ │ │ │ +B9EF5 GID Size 04 (4) │ │ │ │ +B9EF6 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9EFA CENTRAL HEADER #48 02014B50 (33639248) │ │ │ │ +B9EFE Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9EFF Created OS 03 (3) 'Unix' │ │ │ │ +B9F00 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9F01 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9F02 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9F04 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9F06 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9F0A CRC 405AA2A7 (1079681703) │ │ │ │ +B9F0E Compressed Size 00000535 (1333) │ │ │ │ +B9F12 Uncompressed Size 00000C96 (3222) │ │ │ │ +B9F16 Filename Length 0017 (23) │ │ │ │ +B9F18 Extra Length 0018 (24) │ │ │ │ +B9F1A Comment Length 0000 (0) │ │ │ │ +B9F1C Disk Start 0000 (0) │ │ │ │ +B9F1E Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9F20 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9F24 Local Header Offset 000537FB (342011) │ │ │ │ +B9F28 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9F28: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9F3F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9F41 Length 0005 (5) │ │ │ │ +B9F43 Flags 01 (1) 'Modification' │ │ │ │ +B9F44 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9F48 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9F4A Length 000B (11) │ │ │ │ +B9F4C Version 01 (1) │ │ │ │ +B9F4D UID Size 04 (4) │ │ │ │ +B9F4E UID 00000000 (0) │ │ │ │ +B9F52 GID Size 04 (4) │ │ │ │ +B9F53 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9F57 CENTRAL HEADER #49 02014B50 (33639248) │ │ │ │ +B9F5B Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9F5C Created OS 03 (3) 'Unix' │ │ │ │ +B9F5D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9F5E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9F5F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9F61 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9F63 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9F67 CRC DB679B08 (3681000200) │ │ │ │ +B9F6B Compressed Size 00000467 (1127) │ │ │ │ +B9F6F Uncompressed Size 00000931 (2353) │ │ │ │ +B9F73 Filename Length 001B (27) │ │ │ │ +B9F75 Extra Length 0018 (24) │ │ │ │ +B9F77 Comment Length 0000 (0) │ │ │ │ +B9F79 Disk Start 0000 (0) │ │ │ │ +B9F7B Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9F7D Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9F81 Local Header Offset 00053D81 (343425) │ │ │ │ +B9F85 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9F85: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +B9FA0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +B9FA2 Length 0005 (5) │ │ │ │ +B9FA4 Flags 01 (1) 'Modification' │ │ │ │ +B9FA5 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +B9FA9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +B9FAB Length 000B (11) │ │ │ │ +B9FAD Version 01 (1) │ │ │ │ +B9FAE UID Size 04 (4) │ │ │ │ +B9FAF UID 00000000 (0) │ │ │ │ +B9FB3 GID Size 04 (4) │ │ │ │ +B9FB4 GID 00000000 (0) │ │ │ │ + │ │ │ │ +B9FB8 CENTRAL HEADER #50 02014B50 (33639248) │ │ │ │ +B9FBC Created Zip Spec 3D (61) '6.1' │ │ │ │ +B9FBD Created OS 03 (3) 'Unix' │ │ │ │ +B9FBE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +B9FBF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +B9FC0 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +B9FC2 Compression Method 0008 (8) 'Deflated' │ │ │ │ +B9FC4 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +B9FC8 CRC 151A8F75 (354062197) │ │ │ │ +B9FCC Compressed Size 000016F1 (5873) │ │ │ │ +B9FD0 Uncompressed Size 00007A6D (31341) │ │ │ │ +B9FD4 Filename Length 001F (31) │ │ │ │ +B9FD6 Extra Length 0018 (24) │ │ │ │ +B9FD8 Comment Length 0000 (0) │ │ │ │ +B9FDA Disk Start 0000 (0) │ │ │ │ +B9FDC Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +B9FDE Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +B9FE2 Local Header Offset 0005423D (344637) │ │ │ │ +B9FE6 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xB9FE6: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA005 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA007 Length 0005 (5) │ │ │ │ +BA009 Flags 01 (1) 'Modification' │ │ │ │ +BA00A Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA00E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA010 Length 000B (11) │ │ │ │ +BA012 Version 01 (1) │ │ │ │ +BA013 UID Size 04 (4) │ │ │ │ +BA014 UID 00000000 (0) │ │ │ │ +BA018 GID Size 04 (4) │ │ │ │ +BA019 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA01D CENTRAL HEADER #51 02014B50 (33639248) │ │ │ │ +BA021 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA022 Created OS 03 (3) 'Unix' │ │ │ │ +BA023 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA024 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA025 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA027 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA029 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA02D CRC A615B373 (2786440051) │ │ │ │ +BA031 Compressed Size 00004162 (16738) │ │ │ │ +BA035 Uncompressed Size 0001D15F (119135) │ │ │ │ +BA039 Filename Length 0010 (16) │ │ │ │ +BA03B Extra Length 0018 (24) │ │ │ │ +BA03D Comment Length 0000 (0) │ │ │ │ +BA03F Disk Start 0000 (0) │ │ │ │ +BA041 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA043 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA047 Local Header Offset 00055987 (350599) │ │ │ │ +BA04B Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA04B: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA05B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA05D Length 0005 (5) │ │ │ │ +BA05F Flags 01 (1) 'Modification' │ │ │ │ +BA060 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA064 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA066 Length 000B (11) │ │ │ │ +BA068 Version 01 (1) │ │ │ │ +BA069 UID Size 04 (4) │ │ │ │ +BA06A UID 00000000 (0) │ │ │ │ +BA06E GID Size 04 (4) │ │ │ │ +BA06F GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA073 CENTRAL HEADER #52 02014B50 (33639248) │ │ │ │ +BA077 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA078 Created OS 03 (3) 'Unix' │ │ │ │ +BA079 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA07A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA07B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA07D Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA07F Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA083 CRC E65F34EA (3864999146) │ │ │ │ +BA087 Compressed Size 00000AE6 (2790) │ │ │ │ +BA08B Uncompressed Size 00002229 (8745) │ │ │ │ +BA08F Filename Length 0014 (20) │ │ │ │ +BA091 Extra Length 0018 (24) │ │ │ │ +BA093 Comment Length 0000 (0) │ │ │ │ +BA095 Disk Start 0000 (0) │ │ │ │ +BA097 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA099 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA09D Local Header Offset 00059B33 (367411) │ │ │ │ +BA0A1 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA0A1: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA0B5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA0B7 Length 0005 (5) │ │ │ │ +BA0B9 Flags 01 (1) 'Modification' │ │ │ │ +BA0BA Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA0BE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA0C0 Length 000B (11) │ │ │ │ +BA0C2 Version 01 (1) │ │ │ │ +BA0C3 UID Size 04 (4) │ │ │ │ +BA0C4 UID 00000000 (0) │ │ │ │ +BA0C8 GID Size 04 (4) │ │ │ │ +BA0C9 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA0CD CENTRAL HEADER #53 02014B50 (33639248) │ │ │ │ +BA0D1 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA0D2 Created OS 03 (3) 'Unix' │ │ │ │ +BA0D3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA0D4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA0D5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA0D7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA0D9 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA0DD CRC CC7897DB (3430455259) │ │ │ │ +BA0E1 Compressed Size 0000B53D (46397) │ │ │ │ +BA0E5 Uncompressed Size 000418FB (268539) │ │ │ │ +BA0E9 Filename Length 0017 (23) │ │ │ │ +BA0EB Extra Length 0018 (24) │ │ │ │ +BA0ED Comment Length 0000 (0) │ │ │ │ +BA0EF Disk Start 0000 (0) │ │ │ │ +BA0F1 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA0F3 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA0F7 Local Header Offset 0005A667 (370279) │ │ │ │ +BA0FB Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA0FB: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA112 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA114 Length 0005 (5) │ │ │ │ +BA116 Flags 01 (1) 'Modification' │ │ │ │ +BA117 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA11B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA11D Length 000B (11) │ │ │ │ +BA11F Version 01 (1) │ │ │ │ +BA120 UID Size 04 (4) │ │ │ │ +BA121 UID 00000000 (0) │ │ │ │ +BA125 GID Size 04 (4) │ │ │ │ +BA126 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA12A CENTRAL HEADER #54 02014B50 (33639248) │ │ │ │ +BA12E Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA12F Created OS 03 (3) 'Unix' │ │ │ │ +BA130 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA131 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA132 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA134 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA136 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA13A CRC CEBA6EB0 (3468324528) │ │ │ │ +BA13E Compressed Size 000003FF (1023) │ │ │ │ +BA142 Uncompressed Size 0000093D (2365) │ │ │ │ +BA146 Filename Length 0013 (19) │ │ │ │ +BA148 Extra Length 0018 (24) │ │ │ │ +BA14A Comment Length 0000 (0) │ │ │ │ +BA14C Disk Start 0000 (0) │ │ │ │ +BA14E Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA150 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA154 Local Header Offset 00065BF5 (416757) │ │ │ │ +BA158 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA158: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA16B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA16D Length 0005 (5) │ │ │ │ +BA16F Flags 01 (1) 'Modification' │ │ │ │ +BA170 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA174 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA176 Length 000B (11) │ │ │ │ +BA178 Version 01 (1) │ │ │ │ +BA179 UID Size 04 (4) │ │ │ │ +BA17A UID 00000000 (0) │ │ │ │ +BA17E GID Size 04 (4) │ │ │ │ +BA17F GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA183 CENTRAL HEADER #55 02014B50 (33639248) │ │ │ │ +BA187 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA188 Created OS 03 (3) 'Unix' │ │ │ │ +BA189 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA18A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA18B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA18D Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA18F Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA193 CRC FD36DFE1 (4248231905) │ │ │ │ +BA197 Compressed Size 000014D7 (5335) │ │ │ │ +BA19B Uncompressed Size 00006892 (26770) │ │ │ │ +BA19F Filename Length 0012 (18) │ │ │ │ +BA1A1 Extra Length 0018 (24) │ │ │ │ +BA1A3 Comment Length 0000 (0) │ │ │ │ +BA1A5 Disk Start 0000 (0) │ │ │ │ +BA1A7 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA1A9 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA1AD Local Header Offset 00066041 (417857) │ │ │ │ +BA1B1 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA1B1: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA1C3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA1C5 Length 0005 (5) │ │ │ │ +BA1C7 Flags 01 (1) 'Modification' │ │ │ │ +BA1C8 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA1CC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA1CE Length 000B (11) │ │ │ │ +BA1D0 Version 01 (1) │ │ │ │ +BA1D1 UID Size 04 (4) │ │ │ │ +BA1D2 UID 00000000 (0) │ │ │ │ +BA1D6 GID Size 04 (4) │ │ │ │ +BA1D7 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA1DB CENTRAL HEADER #56 02014B50 (33639248) │ │ │ │ +BA1DF Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA1E0 Created OS 03 (3) 'Unix' │ │ │ │ +BA1E1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA1E2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA1E3 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA1E5 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA1E7 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA1EB CRC EC2D0A1B (3962374683) │ │ │ │ +BA1EF Compressed Size 000011EB (4587) │ │ │ │ +BA1F3 Uncompressed Size 000040FA (16634) │ │ │ │ +BA1F7 Filename Length 0012 (18) │ │ │ │ +BA1F9 Extra Length 0018 (24) │ │ │ │ +BA1FB Comment Length 0000 (0) │ │ │ │ +BA1FD Disk Start 0000 (0) │ │ │ │ +BA1FF Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA201 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA205 Local Header Offset 00067564 (423268) │ │ │ │ +BA209 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA209: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA21B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA21D Length 0005 (5) │ │ │ │ +BA21F Flags 01 (1) 'Modification' │ │ │ │ +BA220 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA224 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA226 Length 000B (11) │ │ │ │ +BA228 Version 01 (1) │ │ │ │ +BA229 UID Size 04 (4) │ │ │ │ +BA22A UID 00000000 (0) │ │ │ │ +BA22E GID Size 04 (4) │ │ │ │ +BA22F GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA233 CENTRAL HEADER #57 02014B50 (33639248) │ │ │ │ +BA237 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA238 Created OS 03 (3) 'Unix' │ │ │ │ +BA239 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA23A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA23B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA23D Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA23F Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA243 CRC 34ABD5D4 (883676628) │ │ │ │ +BA247 Compressed Size 000009D8 (2520) │ │ │ │ +BA24B Uncompressed Size 00003529 (13609) │ │ │ │ +BA24F Filename Length 0019 (25) │ │ │ │ +BA251 Extra Length 0018 (24) │ │ │ │ +BA253 Comment Length 0000 (0) │ │ │ │ +BA255 Disk Start 0000 (0) │ │ │ │ +BA257 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA259 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA25D Local Header Offset 0006879B (427931) │ │ │ │ +BA261 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA261: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA27A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA27C Length 0005 (5) │ │ │ │ +BA27E Flags 01 (1) 'Modification' │ │ │ │ +BA27F Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA283 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA285 Length 000B (11) │ │ │ │ +BA287 Version 01 (1) │ │ │ │ +BA288 UID Size 04 (4) │ │ │ │ +BA289 UID 00000000 (0) │ │ │ │ +BA28D GID Size 04 (4) │ │ │ │ +BA28E GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA292 CENTRAL HEADER #58 02014B50 (33639248) │ │ │ │ +BA296 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA297 Created OS 03 (3) 'Unix' │ │ │ │ +BA298 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA299 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA29A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA29C Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA29E Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA2A2 CRC 3CD95A4D (1020877389) │ │ │ │ +BA2A6 Compressed Size 000018B5 (6325) │ │ │ │ +BA2AA Uncompressed Size 0000A678 (42616) │ │ │ │ +BA2AE Filename Length 0019 (25) │ │ │ │ +BA2B0 Extra Length 0018 (24) │ │ │ │ +BA2B2 Comment Length 0000 (0) │ │ │ │ +BA2B4 Disk Start 0000 (0) │ │ │ │ +BA2B6 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA2B8 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA2BC Local Header Offset 000691C6 (430534) │ │ │ │ +BA2C0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA2C0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA2D9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA2DB Length 0005 (5) │ │ │ │ +BA2DD Flags 01 (1) 'Modification' │ │ │ │ +BA2DE Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA2E2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA2E4 Length 000B (11) │ │ │ │ +BA2E6 Version 01 (1) │ │ │ │ +BA2E7 UID Size 04 (4) │ │ │ │ +BA2E8 UID 00000000 (0) │ │ │ │ +BA2EC GID Size 04 (4) │ │ │ │ +BA2ED GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA2F1 CENTRAL HEADER #59 02014B50 (33639248) │ │ │ │ +BA2F5 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA2F6 Created OS 03 (3) 'Unix' │ │ │ │ +BA2F7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA2F8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA2F9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA2FB Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA2FD Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA301 CRC EB9538F3 (3952425203) │ │ │ │ +BA305 Compressed Size 0000177C (6012) │ │ │ │ +BA309 Uncompressed Size 0000472C (18220) │ │ │ │ +BA30D Filename Length 0014 (20) │ │ │ │ +BA30F Extra Length 0018 (24) │ │ │ │ +BA311 Comment Length 0000 (0) │ │ │ │ +BA313 Disk Start 0000 (0) │ │ │ │ +BA315 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA317 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA31B Local Header Offset 0006AACE (436942) │ │ │ │ +BA31F Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA31F: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA333 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA335 Length 0005 (5) │ │ │ │ +BA337 Flags 01 (1) 'Modification' │ │ │ │ +BA338 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA33C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA33E Length 000B (11) │ │ │ │ +BA340 Version 01 (1) │ │ │ │ +BA341 UID Size 04 (4) │ │ │ │ +BA342 UID 00000000 (0) │ │ │ │ +BA346 GID Size 04 (4) │ │ │ │ +BA347 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA34B CENTRAL HEADER #60 02014B50 (33639248) │ │ │ │ +BA34F Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA350 Created OS 03 (3) 'Unix' │ │ │ │ +BA351 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA352 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA353 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA355 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA357 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA35B CRC F6F034FB (4142937339) │ │ │ │ +BA35F Compressed Size 00000409 (1033) │ │ │ │ +BA363 Uncompressed Size 00000825 (2085) │ │ │ │ +BA367 Filename Length 001C (28) │ │ │ │ +BA369 Extra Length 0018 (24) │ │ │ │ +BA36B Comment Length 0000 (0) │ │ │ │ +BA36D Disk Start 0000 (0) │ │ │ │ +BA36F Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA371 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA375 Local Header Offset 0006C298 (443032) │ │ │ │ +BA379 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA379: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA395 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA397 Length 0005 (5) │ │ │ │ +BA399 Flags 01 (1) 'Modification' │ │ │ │ +BA39A Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA39E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA3A0 Length 000B (11) │ │ │ │ +BA3A2 Version 01 (1) │ │ │ │ +BA3A3 UID Size 04 (4) │ │ │ │ +BA3A4 UID 00000000 (0) │ │ │ │ +BA3A8 GID Size 04 (4) │ │ │ │ +BA3A9 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA3AD CENTRAL HEADER #61 02014B50 (33639248) │ │ │ │ +BA3B1 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA3B2 Created OS 03 (3) 'Unix' │ │ │ │ +BA3B3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA3B4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA3B5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA3B7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA3B9 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA3BD CRC D734912E (3610546478) │ │ │ │ +BA3C1 Compressed Size 000024BD (9405) │ │ │ │ +BA3C5 Uncompressed Size 0000B65B (46683) │ │ │ │ +BA3C9 Filename Length 001F (31) │ │ │ │ +BA3CB Extra Length 0018 (24) │ │ │ │ +BA3CD Comment Length 0000 (0) │ │ │ │ +BA3CF Disk Start 0000 (0) │ │ │ │ +BA3D1 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA3D3 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA3D7 Local Header Offset 0006C6F7 (444151) │ │ │ │ +BA3DB Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA3DB: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA3FA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA3FC Length 0005 (5) │ │ │ │ +BA3FE Flags 01 (1) 'Modification' │ │ │ │ +BA3FF Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA403 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA405 Length 000B (11) │ │ │ │ +BA407 Version 01 (1) │ │ │ │ +BA408 UID Size 04 (4) │ │ │ │ +BA409 UID 00000000 (0) │ │ │ │ +BA40D GID Size 04 (4) │ │ │ │ +BA40E GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA412 CENTRAL HEADER #62 02014B50 (33639248) │ │ │ │ +BA416 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA417 Created OS 03 (3) 'Unix' │ │ │ │ +BA418 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA419 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA41A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA41C Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA41E Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA422 CRC 9DF820AC (2650284204) │ │ │ │ +BA426 Compressed Size 00000E7E (3710) │ │ │ │ +BA42A Uncompressed Size 000052D9 (21209) │ │ │ │ +BA42E Filename Length 001F (31) │ │ │ │ +BA430 Extra Length 0018 (24) │ │ │ │ +BA432 Comment Length 0000 (0) │ │ │ │ +BA434 Disk Start 0000 (0) │ │ │ │ +BA436 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA438 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA43C Local Header Offset 0006EC0D (453645) │ │ │ │ +BA440 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA440: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA45F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA461 Length 0005 (5) │ │ │ │ +BA463 Flags 01 (1) 'Modification' │ │ │ │ +BA464 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA468 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA46A Length 000B (11) │ │ │ │ +BA46C Version 01 (1) │ │ │ │ +BA46D UID Size 04 (4) │ │ │ │ +BA46E UID 00000000 (0) │ │ │ │ +BA472 GID Size 04 (4) │ │ │ │ +BA473 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA477 CENTRAL HEADER #63 02014B50 (33639248) │ │ │ │ +BA47B Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA47C Created OS 03 (3) 'Unix' │ │ │ │ +BA47D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA47E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA47F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA481 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA483 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA487 CRC FEC753B6 (4274475958) │ │ │ │ +BA48B Compressed Size 00000A45 (2629) │ │ │ │ +BA48F Uncompressed Size 0000247A (9338) │ │ │ │ +BA493 Filename Length 0013 (19) │ │ │ │ +BA495 Extra Length 0018 (24) │ │ │ │ +BA497 Comment Length 0000 (0) │ │ │ │ +BA499 Disk Start 0000 (0) │ │ │ │ +BA49B Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA49D Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA4A1 Local Header Offset 0006FAE4 (457444) │ │ │ │ +BA4A5 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA4A5: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA4B8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA4BA Length 0005 (5) │ │ │ │ +BA4BC Flags 01 (1) 'Modification' │ │ │ │ +BA4BD Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA4C1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA4C3 Length 000B (11) │ │ │ │ +BA4C5 Version 01 (1) │ │ │ │ +BA4C6 UID Size 04 (4) │ │ │ │ +BA4C7 UID 00000000 (0) │ │ │ │ +BA4CB GID Size 04 (4) │ │ │ │ +BA4CC GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA4D0 CENTRAL HEADER #64 02014B50 (33639248) │ │ │ │ +BA4D4 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA4D5 Created OS 03 (3) 'Unix' │ │ │ │ +BA4D6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA4D7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA4D8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA4DA Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA4DC Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA4E0 CRC 29736947 (695429447) │ │ │ │ +BA4E4 Compressed Size 0000257D (9597) │ │ │ │ +BA4E8 Uncompressed Size 0000BA5F (47711) │ │ │ │ +BA4EC Filename Length 0019 (25) │ │ │ │ +BA4EE Extra Length 0018 (24) │ │ │ │ +BA4F0 Comment Length 0000 (0) │ │ │ │ +BA4F2 Disk Start 0000 (0) │ │ │ │ +BA4F4 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA4F6 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA4FA Local Header Offset 00070576 (460150) │ │ │ │ +BA4FE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA4FE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA517 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA519 Length 0005 (5) │ │ │ │ +BA51B Flags 01 (1) 'Modification' │ │ │ │ +BA51C Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA520 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA522 Length 000B (11) │ │ │ │ +BA524 Version 01 (1) │ │ │ │ +BA525 UID Size 04 (4) │ │ │ │ +BA526 UID 00000000 (0) │ │ │ │ +BA52A GID Size 04 (4) │ │ │ │ +BA52B GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA52F CENTRAL HEADER #65 02014B50 (33639248) │ │ │ │ +BA533 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA534 Created OS 03 (3) 'Unix' │ │ │ │ +BA535 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA536 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA537 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA539 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA53B Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA53F CRC BF3A6C6B (3208277099) │ │ │ │ +BA543 Compressed Size 00000EFB (3835) │ │ │ │ +BA547 Uncompressed Size 00003A2C (14892) │ │ │ │ +BA54B Filename Length 0024 (36) │ │ │ │ +BA54D Extra Length 0018 (24) │ │ │ │ +BA54F Comment Length 0000 (0) │ │ │ │ +BA551 Disk Start 0000 (0) │ │ │ │ +BA553 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA555 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA559 Local Header Offset 00072B46 (469830) │ │ │ │ +BA55D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA55D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA581 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA583 Length 0005 (5) │ │ │ │ +BA585 Flags 01 (1) 'Modification' │ │ │ │ +BA586 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA58A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA58C Length 000B (11) │ │ │ │ +BA58E Version 01 (1) │ │ │ │ +BA58F UID Size 04 (4) │ │ │ │ +BA590 UID 00000000 (0) │ │ │ │ +BA594 GID Size 04 (4) │ │ │ │ +BA595 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA599 CENTRAL HEADER #66 02014B50 (33639248) │ │ │ │ +BA59D Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA59E Created OS 03 (3) 'Unix' │ │ │ │ +BA59F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA5A0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA5A1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA5A3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA5A5 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA5A9 CRC CB6AD0BA (3412775098) │ │ │ │ +BA5AD Compressed Size 00001AEC (6892) │ │ │ │ +BA5B1 Uncompressed Size 00005F82 (24450) │ │ │ │ +BA5B5 Filename Length 0017 (23) │ │ │ │ +BA5B7 Extra Length 0018 (24) │ │ │ │ +BA5B9 Comment Length 0000 (0) │ │ │ │ +BA5BB Disk Start 0000 (0) │ │ │ │ +BA5BD Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA5BF Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA5C3 Local Header Offset 00073A9F (473759) │ │ │ │ +BA5C7 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA5C7: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA5DE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA5E0 Length 0005 (5) │ │ │ │ +BA5E2 Flags 01 (1) 'Modification' │ │ │ │ +BA5E3 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA5E7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA5E9 Length 000B (11) │ │ │ │ +BA5EB Version 01 (1) │ │ │ │ +BA5EC UID Size 04 (4) │ │ │ │ +BA5ED UID 00000000 (0) │ │ │ │ +BA5F1 GID Size 04 (4) │ │ │ │ +BA5F2 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA5F6 CENTRAL HEADER #67 02014B50 (33639248) │ │ │ │ +BA5FA Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA5FB Created OS 03 (3) 'Unix' │ │ │ │ +BA5FC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA5FD Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA5FE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA600 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA602 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA606 CRC 11E32AF1 (300100337) │ │ │ │ +BA60A Compressed Size 00000ED3 (3795) │ │ │ │ +BA60E Uncompressed Size 000038E2 (14562) │ │ │ │ +BA612 Filename Length 0023 (35) │ │ │ │ +BA614 Extra Length 0018 (24) │ │ │ │ +BA616 Comment Length 0000 (0) │ │ │ │ +BA618 Disk Start 0000 (0) │ │ │ │ +BA61A Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA61C Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA620 Local Header Offset 000755DC (480732) │ │ │ │ +BA624 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA624: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA647 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA649 Length 0005 (5) │ │ │ │ +BA64B Flags 01 (1) 'Modification' │ │ │ │ +BA64C Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA650 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA652 Length 000B (11) │ │ │ │ +BA654 Version 01 (1) │ │ │ │ +BA655 UID Size 04 (4) │ │ │ │ +BA656 UID 00000000 (0) │ │ │ │ +BA65A GID Size 04 (4) │ │ │ │ +BA65B GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA65F CENTRAL HEADER #68 02014B50 (33639248) │ │ │ │ +BA663 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA664 Created OS 03 (3) 'Unix' │ │ │ │ +BA665 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA666 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA667 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA669 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA66B Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA66F CRC 2DB7929F (767005343) │ │ │ │ +BA673 Compressed Size 00000113 (275) │ │ │ │ +BA677 Uncompressed Size 000001F3 (499) │ │ │ │ +BA67B Filename Length 001B (27) │ │ │ │ +BA67D Extra Length 0018 (24) │ │ │ │ +BA67F Comment Length 0000 (0) │ │ │ │ +BA681 Disk Start 0000 (0) │ │ │ │ +BA683 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA685 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA689 Local Header Offset 0007650C (484620) │ │ │ │ +BA68D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA68D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA6A8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA6AA Length 0005 (5) │ │ │ │ +BA6AC Flags 01 (1) 'Modification' │ │ │ │ +BA6AD Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA6B1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA6B3 Length 000B (11) │ │ │ │ +BA6B5 Version 01 (1) │ │ │ │ +BA6B6 UID Size 04 (4) │ │ │ │ +BA6B7 UID 00000000 (0) │ │ │ │ +BA6BB GID Size 04 (4) │ │ │ │ +BA6BC GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA6C0 CENTRAL HEADER #69 02014B50 (33639248) │ │ │ │ +BA6C4 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA6C5 Created OS 03 (3) 'Unix' │ │ │ │ +BA6C6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA6C7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA6C8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA6CA Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA6CC Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA6D0 CRC E6350A39 (3862235705) │ │ │ │ +BA6D4 Compressed Size 0000188E (6286) │ │ │ │ +BA6D8 Uncompressed Size 00008FAC (36780) │ │ │ │ +BA6DC Filename Length 001D (29) │ │ │ │ +BA6DE Extra Length 0018 (24) │ │ │ │ +BA6E0 Comment Length 0000 (0) │ │ │ │ +BA6E2 Disk Start 0000 (0) │ │ │ │ +BA6E4 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA6E6 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA6EA Local Header Offset 00076674 (484980) │ │ │ │ +BA6EE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA6EE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA70B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA70D Length 0005 (5) │ │ │ │ +BA70F Flags 01 (1) 'Modification' │ │ │ │ +BA710 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA714 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA716 Length 000B (11) │ │ │ │ +BA718 Version 01 (1) │ │ │ │ +BA719 UID Size 04 (4) │ │ │ │ +BA71A UID 00000000 (0) │ │ │ │ +BA71E GID Size 04 (4) │ │ │ │ +BA71F GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA723 CENTRAL HEADER #70 02014B50 (33639248) │ │ │ │ +BA727 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA728 Created OS 03 (3) 'Unix' │ │ │ │ +BA729 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA72A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA72B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA72D Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA72F Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA733 CRC 905D47D7 (2422032343) │ │ │ │ +BA737 Compressed Size 0000164C (5708) │ │ │ │ +BA73B Uncompressed Size 00003A9B (15003) │ │ │ │ +BA73F Filename Length 0015 (21) │ │ │ │ +BA741 Extra Length 0018 (24) │ │ │ │ +BA743 Comment Length 0000 (0) │ │ │ │ +BA745 Disk Start 0000 (0) │ │ │ │ +BA747 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA749 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA74D Local Header Offset 00077F59 (491353) │ │ │ │ +BA751 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA751: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA766 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA768 Length 0005 (5) │ │ │ │ +BA76A Flags 01 (1) 'Modification' │ │ │ │ +BA76B Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA76F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA771 Length 000B (11) │ │ │ │ +BA773 Version 01 (1) │ │ │ │ +BA774 UID Size 04 (4) │ │ │ │ +BA775 UID 00000000 (0) │ │ │ │ +BA779 GID Size 04 (4) │ │ │ │ +BA77A GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA77E CENTRAL HEADER #71 02014B50 (33639248) │ │ │ │ +BA782 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA783 Created OS 03 (3) 'Unix' │ │ │ │ +BA784 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA785 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA786 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA788 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA78A Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA78E CRC 60E34873 (1625507955) │ │ │ │ +BA792 Compressed Size 000040BC (16572) │ │ │ │ +BA796 Uncompressed Size 00013397 (78743) │ │ │ │ +BA79A Filename Length 0016 (22) │ │ │ │ +BA79C Extra Length 0018 (24) │ │ │ │ +BA79E Comment Length 0000 (0) │ │ │ │ +BA7A0 Disk Start 0000 (0) │ │ │ │ +BA7A2 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA7A4 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA7A8 Local Header Offset 000795F4 (497140) │ │ │ │ +BA7AC Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA7AC: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA7C2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA7C4 Length 0005 (5) │ │ │ │ +BA7C6 Flags 01 (1) 'Modification' │ │ │ │ +BA7C7 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA7CB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA7CD Length 000B (11) │ │ │ │ +BA7CF Version 01 (1) │ │ │ │ +BA7D0 UID Size 04 (4) │ │ │ │ +BA7D1 UID 00000000 (0) │ │ │ │ +BA7D5 GID Size 04 (4) │ │ │ │ +BA7D6 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA7DA CENTRAL HEADER #72 02014B50 (33639248) │ │ │ │ +BA7DE Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA7DF Created OS 03 (3) 'Unix' │ │ │ │ +BA7E0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA7E1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA7E2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA7E4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA7E6 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA7EA CRC 53039E88 (1392746120) │ │ │ │ +BA7EE Compressed Size 00003E86 (16006) │ │ │ │ +BA7F2 Uncompressed Size 0001C17B (115067) │ │ │ │ +BA7F6 Filename Length 0019 (25) │ │ │ │ +BA7F8 Extra Length 0018 (24) │ │ │ │ +BA7FA Comment Length 0000 (0) │ │ │ │ +BA7FC Disk Start 0000 (0) │ │ │ │ +BA7FE Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA800 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA804 Local Header Offset 0007D700 (513792) │ │ │ │ +BA808 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA808: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA821 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA823 Length 0005 (5) │ │ │ │ +BA825 Flags 01 (1) 'Modification' │ │ │ │ +BA826 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA82A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA82C Length 000B (11) │ │ │ │ +BA82E Version 01 (1) │ │ │ │ +BA82F UID Size 04 (4) │ │ │ │ +BA830 UID 00000000 (0) │ │ │ │ +BA834 GID Size 04 (4) │ │ │ │ +BA835 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA839 CENTRAL HEADER #73 02014B50 (33639248) │ │ │ │ +BA83D Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA83E Created OS 03 (3) 'Unix' │ │ │ │ +BA83F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA840 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA841 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA843 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA845 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA849 CRC 7E351E59 (2117410393) │ │ │ │ +BA84D Compressed Size 00000892 (2194) │ │ │ │ +BA851 Uncompressed Size 0000362E (13870) │ │ │ │ +BA855 Filename Length 0011 (17) │ │ │ │ +BA857 Extra Length 0018 (24) │ │ │ │ +BA859 Comment Length 0000 (0) │ │ │ │ +BA85B Disk Start 0000 (0) │ │ │ │ +BA85D Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA85F Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA863 Local Header Offset 000815D9 (529881) │ │ │ │ +BA867 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA867: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA878 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA87A Length 0005 (5) │ │ │ │ +BA87C Flags 01 (1) 'Modification' │ │ │ │ +BA87D Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA881 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA883 Length 000B (11) │ │ │ │ +BA885 Version 01 (1) │ │ │ │ +BA886 UID Size 04 (4) │ │ │ │ +BA887 UID 00000000 (0) │ │ │ │ +BA88B GID Size 04 (4) │ │ │ │ +BA88C GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA890 CENTRAL HEADER #74 02014B50 (33639248) │ │ │ │ +BA894 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA895 Created OS 03 (3) 'Unix' │ │ │ │ +BA896 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA897 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA898 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA89A Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA89C Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA8A0 CRC DE1DF177 (3726504311) │ │ │ │ +BA8A4 Compressed Size 000051B8 (20920) │ │ │ │ +BA8A8 Uncompressed Size 0001FBF7 (130039) │ │ │ │ +BA8AC Filename Length 0015 (21) │ │ │ │ +BA8AE Extra Length 0018 (24) │ │ │ │ +BA8B0 Comment Length 0000 (0) │ │ │ │ +BA8B2 Disk Start 0000 (0) │ │ │ │ +BA8B4 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA8B6 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA8BA Local Header Offset 00081EB6 (532150) │ │ │ │ +BA8BE Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA8BE: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA8D3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA8D5 Length 0005 (5) │ │ │ │ +BA8D7 Flags 01 (1) 'Modification' │ │ │ │ +BA8D8 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA8DC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA8DE Length 000B (11) │ │ │ │ +BA8E0 Version 01 (1) │ │ │ │ +BA8E1 UID Size 04 (4) │ │ │ │ +BA8E2 UID 00000000 (0) │ │ │ │ +BA8E6 GID Size 04 (4) │ │ │ │ +BA8E7 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA8EB CENTRAL HEADER #75 02014B50 (33639248) │ │ │ │ +BA8EF Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA8F0 Created OS 03 (3) 'Unix' │ │ │ │ +BA8F1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA8F2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA8F3 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA8F5 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA8F7 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA8FB CRC 587B6125 (1484480805) │ │ │ │ +BA8FF Compressed Size 00001C42 (7234) │ │ │ │ +BA903 Uncompressed Size 00008AC2 (35522) │ │ │ │ +BA907 Filename Length 0019 (25) │ │ │ │ +BA909 Extra Length 0018 (24) │ │ │ │ +BA90B Comment Length 0000 (0) │ │ │ │ +BA90D Disk Start 0000 (0) │ │ │ │ +BA90F Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA911 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA915 Local Header Offset 000870BD (553149) │ │ │ │ +BA919 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA919: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA932 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA934 Length 0005 (5) │ │ │ │ +BA936 Flags 01 (1) 'Modification' │ │ │ │ +BA937 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA93B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA93D Length 000B (11) │ │ │ │ +BA93F Version 01 (1) │ │ │ │ +BA940 UID Size 04 (4) │ │ │ │ +BA941 UID 00000000 (0) │ │ │ │ +BA945 GID Size 04 (4) │ │ │ │ +BA946 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA94A CENTRAL HEADER #76 02014B50 (33639248) │ │ │ │ +BA94E Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA94F Created OS 03 (3) 'Unix' │ │ │ │ +BA950 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA951 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA952 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA954 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA956 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA95A CRC 0EC11074 (247533684) │ │ │ │ +BA95E Compressed Size 00000D95 (3477) │ │ │ │ +BA962 Uncompressed Size 00002E9F (11935) │ │ │ │ +BA966 Filename Length 0018 (24) │ │ │ │ +BA968 Extra Length 0018 (24) │ │ │ │ +BA96A Comment Length 0000 (0) │ │ │ │ +BA96C Disk Start 0000 (0) │ │ │ │ +BA96E Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA970 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA974 Local Header Offset 00088D52 (560466) │ │ │ │ +BA978 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA978: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA990 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA992 Length 0005 (5) │ │ │ │ +BA994 Flags 01 (1) 'Modification' │ │ │ │ +BA995 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA999 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA99B Length 000B (11) │ │ │ │ +BA99D Version 01 (1) │ │ │ │ +BA99E UID Size 04 (4) │ │ │ │ +BA99F UID 00000000 (0) │ │ │ │ +BA9A3 GID Size 04 (4) │ │ │ │ +BA9A4 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA9A8 CENTRAL HEADER #77 02014B50 (33639248) │ │ │ │ +BA9AC Created Zip Spec 3D (61) '6.1' │ │ │ │ +BA9AD Created OS 03 (3) 'Unix' │ │ │ │ +BA9AE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BA9AF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BA9B0 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BA9B2 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BA9B4 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BA9B8 CRC E5DE5170 (3856552304) │ │ │ │ +BA9BC Compressed Size 000001DF (479) │ │ │ │ +BA9C0 Uncompressed Size 00000323 (803) │ │ │ │ +BA9C4 Filename Length 0011 (17) │ │ │ │ +BA9C6 Extra Length 0018 (24) │ │ │ │ +BA9C8 Comment Length 0000 (0) │ │ │ │ +BA9CA Disk Start 0000 (0) │ │ │ │ +BA9CC Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BA9CE Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BA9D2 Local Header Offset 00089B39 (564025) │ │ │ │ +BA9D6 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBA9D6: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BA9E7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BA9E9 Length 0005 (5) │ │ │ │ +BA9EB Flags 01 (1) 'Modification' │ │ │ │ +BA9EC Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BA9F0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BA9F2 Length 000B (11) │ │ │ │ +BA9F4 Version 01 (1) │ │ │ │ +BA9F5 UID Size 04 (4) │ │ │ │ +BA9F6 UID 00000000 (0) │ │ │ │ +BA9FA GID Size 04 (4) │ │ │ │ +BA9FB GID 00000000 (0) │ │ │ │ + │ │ │ │ +BA9FF CENTRAL HEADER #78 02014B50 (33639248) │ │ │ │ +BAA03 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BAA04 Created OS 03 (3) 'Unix' │ │ │ │ +BAA05 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BAA06 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BAA07 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BAA09 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BAA0B Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BAA0F CRC AA4987E4 (2856945636) │ │ │ │ +BAA13 Compressed Size 000006BD (1725) │ │ │ │ +BAA17 Uncompressed Size 0000141E (5150) │ │ │ │ +BAA1B Filename Length 0019 (25) │ │ │ │ +BAA1D Extra Length 0018 (24) │ │ │ │ +BAA1F Comment Length 0000 (0) │ │ │ │ +BAA21 Disk Start 0000 (0) │ │ │ │ +BAA23 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BAA25 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BAA29 Local Header Offset 00089D63 (564579) │ │ │ │ +BAA2D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBAA2D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BAA46 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BAA48 Length 0005 (5) │ │ │ │ +BAA4A Flags 01 (1) 'Modification' │ │ │ │ +BAA4B Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BAA4F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BAA51 Length 000B (11) │ │ │ │ +BAA53 Version 01 (1) │ │ │ │ +BAA54 UID Size 04 (4) │ │ │ │ +BAA55 UID 00000000 (0) │ │ │ │ +BAA59 GID Size 04 (4) │ │ │ │ +BAA5A GID 00000000 (0) │ │ │ │ + │ │ │ │ +BAA5E CENTRAL HEADER #79 02014B50 (33639248) │ │ │ │ +BAA62 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BAA63 Created OS 03 (3) 'Unix' │ │ │ │ +BAA64 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BAA65 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BAA66 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BAA68 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BAA6A Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BAA6E CRC 594C717E (1498182014) │ │ │ │ +BAA72 Compressed Size 00001B89 (7049) │ │ │ │ +BAA76 Uncompressed Size 00009F5F (40799) │ │ │ │ +BAA7A Filename Length 0018 (24) │ │ │ │ +BAA7C Extra Length 0018 (24) │ │ │ │ +BAA7E Comment Length 0000 (0) │ │ │ │ +BAA80 Disk Start 0000 (0) │ │ │ │ +BAA82 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BAA84 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BAA88 Local Header Offset 0008A473 (566387) │ │ │ │ +BAA8C Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBAA8C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BAAA4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BAAA6 Length 0005 (5) │ │ │ │ +BAAA8 Flags 01 (1) 'Modification' │ │ │ │ +BAAA9 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BAAAD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BAAAF Length 000B (11) │ │ │ │ +BAAB1 Version 01 (1) │ │ │ │ +BAAB2 UID Size 04 (4) │ │ │ │ +BAAB3 UID 00000000 (0) │ │ │ │ +BAAB7 GID Size 04 (4) │ │ │ │ +BAAB8 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BAABC CENTRAL HEADER #80 02014B50 (33639248) │ │ │ │ +BAAC0 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BAAC1 Created OS 03 (3) 'Unix' │ │ │ │ +BAAC2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BAAC3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BAAC4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BAAC6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BAAC8 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BAACC CRC 0132E09D (20111517) │ │ │ │ +BAAD0 Compressed Size 000016FE (5886) │ │ │ │ +BAAD4 Uncompressed Size 00008B12 (35602) │ │ │ │ +BAAD8 Filename Length 0012 (18) │ │ │ │ +BAADA Extra Length 0018 (24) │ │ │ │ +BAADC Comment Length 0000 (0) │ │ │ │ +BAADE Disk Start 0000 (0) │ │ │ │ +BAAE0 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BAAE2 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BAAE6 Local Header Offset 0008C04E (573518) │ │ │ │ +BAAEA Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBAAEA: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BAAFC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BAAFE Length 0005 (5) │ │ │ │ +BAB00 Flags 01 (1) 'Modification' │ │ │ │ +BAB01 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BAB05 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BAB07 Length 000B (11) │ │ │ │ +BAB09 Version 01 (1) │ │ │ │ +BAB0A UID Size 04 (4) │ │ │ │ +BAB0B UID 00000000 (0) │ │ │ │ +BAB0F GID Size 04 (4) │ │ │ │ +BAB10 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BAB14 CENTRAL HEADER #81 02014B50 (33639248) │ │ │ │ +BAB18 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BAB19 Created OS 03 (3) 'Unix' │ │ │ │ +BAB1A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BAB1B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BAB1C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BAB1E Compression Method 0008 (8) 'Deflated' │ │ │ │ +BAB20 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BAB24 CRC 8CB6D480 (2360792192) │ │ │ │ +BAB28 Compressed Size 00001E10 (7696) │ │ │ │ +BAB2C Uncompressed Size 00008803 (34819) │ │ │ │ +BAB30 Filename Length 0016 (22) │ │ │ │ +BAB32 Extra Length 0018 (24) │ │ │ │ +BAB34 Comment Length 0000 (0) │ │ │ │ +BAB36 Disk Start 0000 (0) │ │ │ │ +BAB38 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BAB3A Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BAB3E Local Header Offset 0008D798 (579480) │ │ │ │ +BAB42 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBAB42: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BAB58 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BAB5A Length 0005 (5) │ │ │ │ +BAB5C Flags 01 (1) 'Modification' │ │ │ │ +BAB5D Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BAB61 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BAB63 Length 000B (11) │ │ │ │ +BAB65 Version 01 (1) │ │ │ │ +BAB66 UID Size 04 (4) │ │ │ │ +BAB67 UID 00000000 (0) │ │ │ │ +BAB6B GID Size 04 (4) │ │ │ │ +BAB6C GID 00000000 (0) │ │ │ │ + │ │ │ │ +BAB70 CENTRAL HEADER #82 02014B50 (33639248) │ │ │ │ +BAB74 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BAB75 Created OS 03 (3) 'Unix' │ │ │ │ +BAB76 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BAB77 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BAB78 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BAB7A Compression Method 0008 (8) 'Deflated' │ │ │ │ +BAB7C Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BAB80 CRC 76A08905 (1990232325) │ │ │ │ +BAB84 Compressed Size 000029A6 (10662) │ │ │ │ +BAB88 Uncompressed Size 0000D04F (53327) │ │ │ │ +BAB8C Filename Length 001A (26) │ │ │ │ +BAB8E Extra Length 0018 (24) │ │ │ │ +BAB90 Comment Length 0000 (0) │ │ │ │ +BAB92 Disk Start 0000 (0) │ │ │ │ +BAB94 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BAB96 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BAB9A Local Header Offset 0008F5F8 (587256) │ │ │ │ +BAB9E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBAB9E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BABB8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BABBA Length 0005 (5) │ │ │ │ +BABBC Flags 01 (1) 'Modification' │ │ │ │ +BABBD Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BABC1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BABC3 Length 000B (11) │ │ │ │ +BABC5 Version 01 (1) │ │ │ │ +BABC6 UID Size 04 (4) │ │ │ │ +BABC7 UID 00000000 (0) │ │ │ │ +BABCB GID Size 04 (4) │ │ │ │ +BABCC GID 00000000 (0) │ │ │ │ + │ │ │ │ +BABD0 CENTRAL HEADER #83 02014B50 (33639248) │ │ │ │ +BABD4 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BABD5 Created OS 03 (3) 'Unix' │ │ │ │ +BABD6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BABD7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BABD8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BABDA Compression Method 0008 (8) 'Deflated' │ │ │ │ +BABDC Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BABE0 CRC BA402FE5 (3124768741) │ │ │ │ +BABE4 Compressed Size 000009AB (2475) │ │ │ │ +BABE8 Uncompressed Size 00001DB6 (7606) │ │ │ │ +BABEC Filename Length 0018 (24) │ │ │ │ +BABEE Extra Length 0018 (24) │ │ │ │ +BABF0 Comment Length 0000 (0) │ │ │ │ +BABF2 Disk Start 0000 (0) │ │ │ │ +BABF4 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BABF6 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BABFA Local Header Offset 00091FF2 (598002) │ │ │ │ +BABFE Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBABFE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BAC16 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BAC18 Length 0005 (5) │ │ │ │ +BAC1A Flags 01 (1) 'Modification' │ │ │ │ +BAC1B Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BAC1F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BAC21 Length 000B (11) │ │ │ │ +BAC23 Version 01 (1) │ │ │ │ +BAC24 UID Size 04 (4) │ │ │ │ +BAC25 UID 00000000 (0) │ │ │ │ +BAC29 GID Size 04 (4) │ │ │ │ +BAC2A GID 00000000 (0) │ │ │ │ + │ │ │ │ +BAC2E CENTRAL HEADER #84 02014B50 (33639248) │ │ │ │ +BAC32 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BAC33 Created OS 03 (3) 'Unix' │ │ │ │ +BAC34 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BAC35 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BAC36 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BAC38 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BAC3A Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BAC3E CRC F0556E9A (4032130714) │ │ │ │ +BAC42 Compressed Size 000152EE (86766) │ │ │ │ +BAC46 Uncompressed Size 000159F8 (88568) │ │ │ │ +BAC4A Filename Length 001E (30) │ │ │ │ +BAC4C Extra Length 0018 (24) │ │ │ │ +BAC4E Comment Length 0000 (0) │ │ │ │ +BAC50 Disk Start 0000 (0) │ │ │ │ +BAC52 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BAC54 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BAC58 Local Header Offset 000929EF (600559) │ │ │ │ +BAC5C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBAC5C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BAC7A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BAC7C Length 0005 (5) │ │ │ │ +BAC7E Flags 01 (1) 'Modification' │ │ │ │ +BAC7F Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BAC83 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BAC85 Length 000B (11) │ │ │ │ +BAC87 Version 01 (1) │ │ │ │ +BAC88 UID Size 04 (4) │ │ │ │ +BAC89 UID 00000000 (0) │ │ │ │ +BAC8D GID Size 04 (4) │ │ │ │ +BAC8E GID 00000000 (0) │ │ │ │ + │ │ │ │ +BAC92 CENTRAL HEADER #85 02014B50 (33639248) │ │ │ │ +BAC96 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BAC97 Created OS 03 (3) 'Unix' │ │ │ │ +BAC98 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BAC99 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BAC9A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BAC9C Compression Method 0008 (8) 'Deflated' │ │ │ │ +BAC9E Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BACA2 CRC F5E2129F (4125233823) │ │ │ │ +BACA6 Compressed Size 000016BC (5820) │ │ │ │ +BACAA Uncompressed Size 000016CD (5837) │ │ │ │ +BACAE Filename Length 0015 (21) │ │ │ │ +BACB0 Extra Length 0018 (24) │ │ │ │ +BACB2 Comment Length 0000 (0) │ │ │ │ +BACB4 Disk Start 0000 (0) │ │ │ │ +BACB6 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BACB8 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BACBC Local Header Offset 000A7D35 (687413) │ │ │ │ +BACC0 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBACC0: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BACD5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BACD7 Length 0005 (5) │ │ │ │ +BACD9 Flags 01 (1) 'Modification' │ │ │ │ +BACDA Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BACDE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BACE0 Length 000B (11) │ │ │ │ +BACE2 Version 01 (1) │ │ │ │ +BACE3 UID Size 04 (4) │ │ │ │ +BACE4 UID 00000000 (0) │ │ │ │ +BACE8 GID Size 04 (4) │ │ │ │ +BACE9 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BACED CENTRAL HEADER #86 02014B50 (33639248) │ │ │ │ +BACF1 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BACF2 Created OS 03 (3) 'Unix' │ │ │ │ +BACF3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BACF4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BACF5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BACF7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BACF9 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BACFD CRC F5E2129F (4125233823) │ │ │ │ +BAD01 Compressed Size 000016BC (5820) │ │ │ │ +BAD05 Uncompressed Size 000016CD (5837) │ │ │ │ +BAD09 Filename Length 001C (28) │ │ │ │ +BAD0B Extra Length 0018 (24) │ │ │ │ +BAD0D Comment Length 0000 (0) │ │ │ │ +BAD0F Disk Start 0000 (0) │ │ │ │ +BAD11 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BAD13 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BAD17 Local Header Offset 000A9440 (693312) │ │ │ │ +BAD1B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBAD1B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BAD37 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BAD39 Length 0005 (5) │ │ │ │ +BAD3B Flags 01 (1) 'Modification' │ │ │ │ +BAD3C Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BAD40 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BAD42 Length 000B (11) │ │ │ │ +BAD44 Version 01 (1) │ │ │ │ +BAD45 UID Size 04 (4) │ │ │ │ +BAD46 UID 00000000 (0) │ │ │ │ +BAD4A GID Size 04 (4) │ │ │ │ +BAD4B GID 00000000 (0) │ │ │ │ + │ │ │ │ +BAD4F CENTRAL HEADER #87 02014B50 (33639248) │ │ │ │ +BAD53 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BAD54 Created OS 03 (3) 'Unix' │ │ │ │ +BAD55 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +BAD56 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BAD57 General Purpose Flag 0000 (0) │ │ │ │ +BAD59 Compression Method 0000 (0) 'Stored' │ │ │ │ +BAD5B Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BAD5F CRC FC95F24B (4237685323) │ │ │ │ +BAD63 Compressed Size 00001B84 (7044) │ │ │ │ +BAD67 Uncompressed Size 00001B84 (7044) │ │ │ │ +BAD6B Filename Length 0016 (22) │ │ │ │ +BAD6D Extra Length 0018 (24) │ │ │ │ +BAD6F Comment Length 0000 (0) │ │ │ │ +BAD71 Disk Start 0000 (0) │ │ │ │ +BAD73 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BAD75 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BAD79 Local Header Offset 000AAB52 (699218) │ │ │ │ +BAD7D Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBAD7D: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BAD93 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BAD95 Length 0005 (5) │ │ │ │ +BAD97 Flags 01 (1) 'Modification' │ │ │ │ +BAD98 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BAD9C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BAD9E Length 000B (11) │ │ │ │ +BADA0 Version 01 (1) │ │ │ │ +BADA1 UID Size 04 (4) │ │ │ │ +BADA2 UID 00000000 (0) │ │ │ │ +BADA6 GID Size 04 (4) │ │ │ │ +BADA7 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BADAB CENTRAL HEADER #88 02014B50 (33639248) │ │ │ │ +BADAF Created Zip Spec 3D (61) '6.1' │ │ │ │ +BADB0 Created OS 03 (3) 'Unix' │ │ │ │ +BADB1 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +BADB2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BADB3 General Purpose Flag 0000 (0) │ │ │ │ +BADB5 Compression Method 0000 (0) 'Stored' │ │ │ │ +BADB7 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BADBB CRC D0D71F86 (3503759238) │ │ │ │ +BADBF Compressed Size 00000B7B (2939) │ │ │ │ +BADC3 Uncompressed Size 00000B7B (2939) │ │ │ │ +BADC7 Filename Length 0016 (22) │ │ │ │ +BADC9 Extra Length 0018 (24) │ │ │ │ +BADCB Comment Length 0000 (0) │ │ │ │ +BADCD Disk Start 0000 (0) │ │ │ │ +BADCF Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BADD1 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BADD5 Local Header Offset 000AC726 (706342) │ │ │ │ +BADD9 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBADD9: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BADEF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BADF1 Length 0005 (5) │ │ │ │ +BADF3 Flags 01 (1) 'Modification' │ │ │ │ +BADF4 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BADF8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BADFA Length 000B (11) │ │ │ │ +BADFC Version 01 (1) │ │ │ │ +BADFD UID Size 04 (4) │ │ │ │ +BADFE UID 00000000 (0) │ │ │ │ +BAE02 GID Size 04 (4) │ │ │ │ +BAE03 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BAE07 CENTRAL HEADER #89 02014B50 (33639248) │ │ │ │ +BAE0B Created Zip Spec 3D (61) '6.1' │ │ │ │ +BAE0C Created OS 03 (3) 'Unix' │ │ │ │ +BAE0D Extract Zip Spec 0A (10) '1.0' │ │ │ │ +BAE0E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BAE0F General Purpose Flag 0000 (0) │ │ │ │ +BAE11 Compression Method 0000 (0) 'Stored' │ │ │ │ +BAE13 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BAE17 CRC FFF9C4D2 (4294558930) │ │ │ │ +BAE1B Compressed Size 0000138F (5007) │ │ │ │ +BAE1F Uncompressed Size 0000138F (5007) │ │ │ │ +BAE23 Filename Length 0016 (22) │ │ │ │ +BAE25 Extra Length 0018 (24) │ │ │ │ +BAE27 Comment Length 0000 (0) │ │ │ │ +BAE29 Disk Start 0000 (0) │ │ │ │ +BAE2B Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BAE2D Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BAE31 Local Header Offset 000AD2F1 (709361) │ │ │ │ +BAE35 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBAE35: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BAE4B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BAE4D Length 0005 (5) │ │ │ │ +BAE4F Flags 01 (1) 'Modification' │ │ │ │ +BAE50 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BAE54 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BAE56 Length 000B (11) │ │ │ │ +BAE58 Version 01 (1) │ │ │ │ +BAE59 UID Size 04 (4) │ │ │ │ +BAE5A UID 00000000 (0) │ │ │ │ +BAE5E GID Size 04 (4) │ │ │ │ +BAE5F GID 00000000 (0) │ │ │ │ + │ │ │ │ +BAE63 CENTRAL HEADER #90 02014B50 (33639248) │ │ │ │ +BAE67 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BAE68 Created OS 03 (3) 'Unix' │ │ │ │ +BAE69 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +BAE6A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BAE6B General Purpose Flag 0000 (0) │ │ │ │ +BAE6D Compression Method 0000 (0) 'Stored' │ │ │ │ +BAE6F Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BAE73 CRC A1037E8E (2701360782) │ │ │ │ +BAE77 Compressed Size 0000145E (5214) │ │ │ │ +BAE7B Uncompressed Size 0000145E (5214) │ │ │ │ +BAE7F Filename Length 0016 (22) │ │ │ │ +BAE81 Extra Length 0018 (24) │ │ │ │ +BAE83 Comment Length 0000 (0) │ │ │ │ +BAE85 Disk Start 0000 (0) │ │ │ │ +BAE87 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BAE89 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BAE8D Local Header Offset 000AE6D0 (714448) │ │ │ │ +BAE91 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBAE91: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BAEA7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BAEA9 Length 0005 (5) │ │ │ │ +BAEAB Flags 01 (1) 'Modification' │ │ │ │ +BAEAC Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BAEB0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BAEB2 Length 000B (11) │ │ │ │ +BAEB4 Version 01 (1) │ │ │ │ +BAEB5 UID Size 04 (4) │ │ │ │ +BAEB6 UID 00000000 (0) │ │ │ │ +BAEBA GID Size 04 (4) │ │ │ │ +BAEBB GID 00000000 (0) │ │ │ │ + │ │ │ │ +BAEBF CENTRAL HEADER #91 02014B50 (33639248) │ │ │ │ +BAEC3 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BAEC4 Created OS 03 (3) 'Unix' │ │ │ │ +BAEC5 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +BAEC6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BAEC7 General Purpose Flag 0000 (0) │ │ │ │ +BAEC9 Compression Method 0000 (0) 'Stored' │ │ │ │ +BAECB Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BAECF CRC 5E9E64F1 (1587438833) │ │ │ │ +BAED3 Compressed Size 000008EC (2284) │ │ │ │ +BAED7 Uncompressed Size 000008EC (2284) │ │ │ │ +BAEDB Filename Length 0016 (22) │ │ │ │ +BAEDD Extra Length 0018 (24) │ │ │ │ +BAEDF Comment Length 0000 (0) │ │ │ │ +BAEE1 Disk Start 0000 (0) │ │ │ │ +BAEE3 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BAEE5 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BAEE9 Local Header Offset 000AFB7E (719742) │ │ │ │ +BAEED Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBAEED: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BAF03 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BAF05 Length 0005 (5) │ │ │ │ +BAF07 Flags 01 (1) 'Modification' │ │ │ │ +BAF08 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BAF0C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BAF0E Length 000B (11) │ │ │ │ +BAF10 Version 01 (1) │ │ │ │ +BAF11 UID Size 04 (4) │ │ │ │ +BAF12 UID 00000000 (0) │ │ │ │ +BAF16 GID Size 04 (4) │ │ │ │ +BAF17 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BAF1B CENTRAL HEADER #92 02014B50 (33639248) │ │ │ │ +BAF1F Created Zip Spec 3D (61) '6.1' │ │ │ │ +BAF20 Created OS 03 (3) 'Unix' │ │ │ │ +BAF21 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +BAF22 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BAF23 General Purpose Flag 0000 (0) │ │ │ │ +BAF25 Compression Method 0000 (0) 'Stored' │ │ │ │ +BAF27 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BAF2B CRC 42E340AB (1122189483) │ │ │ │ +BAF2F Compressed Size 00001F2E (7982) │ │ │ │ +BAF33 Uncompressed Size 00001F2E (7982) │ │ │ │ +BAF37 Filename Length 001E (30) │ │ │ │ +BAF39 Extra Length 0018 (24) │ │ │ │ +BAF3B Comment Length 0000 (0) │ │ │ │ +BAF3D Disk Start 0000 (0) │ │ │ │ +BAF3F Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BAF41 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BAF45 Local Header Offset 000B04BA (722106) │ │ │ │ +BAF49 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBAF49: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BAF67 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BAF69 Length 0005 (5) │ │ │ │ +BAF6B Flags 01 (1) 'Modification' │ │ │ │ +BAF6C Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BAF70 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BAF72 Length 000B (11) │ │ │ │ +BAF74 Version 01 (1) │ │ │ │ +BAF75 UID Size 04 (4) │ │ │ │ +BAF76 UID 00000000 (0) │ │ │ │ +BAF7A GID Size 04 (4) │ │ │ │ +BAF7B GID 00000000 (0) │ │ │ │ + │ │ │ │ +BAF7F CENTRAL HEADER #93 02014B50 (33639248) │ │ │ │ +BAF83 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BAF84 Created OS 03 (3) 'Unix' │ │ │ │ +BAF85 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BAF86 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BAF87 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BAF89 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BAF8B Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BAF8F CRC 2BF6ADFD (737586685) │ │ │ │ +BAF93 Compressed Size 00003D74 (15732) │ │ │ │ +BAF97 Uncompressed Size 0001664F (91727) │ │ │ │ +BAF9B Filename Length 001A (26) │ │ │ │ +BAF9D Extra Length 0018 (24) │ │ │ │ +BAF9F Comment Length 0000 (0) │ │ │ │ +BAFA1 Disk Start 0000 (0) │ │ │ │ +BAFA3 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BAFA5 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BAFA9 Local Header Offset 000B2440 (730176) │ │ │ │ +BAFAD Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBAFAD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BAFC7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BAFC9 Length 0005 (5) │ │ │ │ +BAFCB Flags 01 (1) 'Modification' │ │ │ │ +BAFCC Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BAFD0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BAFD2 Length 000B (11) │ │ │ │ +BAFD4 Version 01 (1) │ │ │ │ +BAFD5 UID Size 04 (4) │ │ │ │ +BAFD6 UID 00000000 (0) │ │ │ │ +BAFDA GID Size 04 (4) │ │ │ │ +BAFDB GID 00000000 (0) │ │ │ │ + │ │ │ │ +BAFDF CENTRAL HEADER #94 02014B50 (33639248) │ │ │ │ +BAFE3 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BAFE4 Created OS 03 (3) 'Unix' │ │ │ │ +BAFE5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BAFE6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BAFE7 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BAFE9 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BAFEB Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BAFEF CRC 67582E4E (1733832270) │ │ │ │ +BAFF3 Compressed Size 000029CF (10703) │ │ │ │ +BAFF7 Uncompressed Size 0000BB39 (47929) │ │ │ │ +BAFFB Filename Length 0018 (24) │ │ │ │ +BAFFD Extra Length 0018 (24) │ │ │ │ +BAFFF Comment Length 0000 (0) │ │ │ │ +BB001 Disk Start 0000 (0) │ │ │ │ +BB003 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BB005 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BB009 Local Header Offset 000B6208 (745992) │ │ │ │ +BB00D Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBB00D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BB025 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BB027 Length 0005 (5) │ │ │ │ +BB029 Flags 01 (1) 'Modification' │ │ │ │ +BB02A Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BB02E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BB030 Length 000B (11) │ │ │ │ +BB032 Version 01 (1) │ │ │ │ +BB033 UID Size 04 (4) │ │ │ │ +BB034 UID 00000000 (0) │ │ │ │ +BB038 GID Size 04 (4) │ │ │ │ +BB039 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BB03D CENTRAL HEADER #95 02014B50 (33639248) │ │ │ │ +BB041 Created Zip Spec 3D (61) '6.1' │ │ │ │ +BB042 Created OS 03 (3) 'Unix' │ │ │ │ +BB043 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BB044 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BB045 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BB047 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BB049 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BB04D CRC DCB3B516 (3702764822) │ │ │ │ +BB051 Compressed Size 000000AE (174) │ │ │ │ +BB055 Uncompressed Size 000000FC (252) │ │ │ │ +BB059 Filename Length 0016 (22) │ │ │ │ +BB05B Extra Length 0018 (24) │ │ │ │ +BB05D Comment Length 0000 (0) │ │ │ │ +BB05F Disk Start 0000 (0) │ │ │ │ +BB061 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BB063 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BB067 Local Header Offset 000B8C29 (756777) │ │ │ │ +BB06B Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBB06B: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BB081 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BB083 Length 0005 (5) │ │ │ │ +BB085 Flags 01 (1) 'Modification' │ │ │ │ +BB086 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BB08A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BB08C Length 000B (11) │ │ │ │ +BB08E Version 01 (1) │ │ │ │ +BB08F UID Size 04 (4) │ │ │ │ +BB090 UID 00000000 (0) │ │ │ │ +BB094 GID Size 04 (4) │ │ │ │ +BB095 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BB099 CENTRAL HEADER #96 02014B50 (33639248) │ │ │ │ +BB09D Created Zip Spec 3D (61) '6.1' │ │ │ │ +BB09E Created OS 03 (3) 'Unix' │ │ │ │ +BB09F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +BB0A0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +BB0A1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +BB0A3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +BB0A5 Modification Time 5C737C9D (1551072413) 'Thu Mar 19 15:36:58 2026' │ │ │ │ +BB0A9 CRC 58439733 (1480824627) │ │ │ │ +BB0AD Compressed Size 00000077 (119) │ │ │ │ +BB0B1 Uncompressed Size 000000A2 (162) │ │ │ │ +BB0B5 Filename Length 002D (45) │ │ │ │ +BB0B7 Extra Length 0018 (24) │ │ │ │ +BB0B9 Comment Length 0000 (0) │ │ │ │ +BB0BB Disk Start 0000 (0) │ │ │ │ +BB0BD Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +BB0BF Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +BB0C3 Local Header Offset 000B8D27 (757031) │ │ │ │ +BB0C7 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xBB0C7: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +BB0F4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +BB0F6 Length 0005 (5) │ │ │ │ +BB0F8 Flags 01 (1) 'Modification' │ │ │ │ +BB0F9 Modification Time 69BC181B (1773934619) 'Thu Mar 19 15:36:59 2026' │ │ │ │ +BB0FD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +BB0FF Length 000B (11) │ │ │ │ +BB101 Version 01 (1) │ │ │ │ +BB102 UID Size 04 (4) │ │ │ │ +BB103 UID 00000000 (0) │ │ │ │ +BB107 GID Size 04 (4) │ │ │ │ +BB108 GID 00000000 (0) │ │ │ │ + │ │ │ │ +BB10C END CENTRAL HEADER 06054B50 (101010256) │ │ │ │ +BB110 Number of this disk 0000 (0) │ │ │ │ +BB112 Central Dir Disk no 0000 (0) │ │ │ │ +BB114 Entries in this disk 0060 (96) │ │ │ │ +BB116 Total Entries 0060 (96) │ │ │ │ +BB118 Size of Central Dir 00002307 (8967) │ │ │ │ +BB11C Offset to Central Dir 000B8E05 (757253) │ │ │ │ +BB120 Comment Length 0000 (0) │ │ │ │ # │ │ │ │ # Warning Count: 192 │ │ │ │ # │ │ │ │ # Done │ │ │ ├── filetype from file(1) │ │ │ │ @@ -1 +1 @@ │ │ │ │ -Zip archive data, made by v6.1 UNIX, extract using at least v1.0, last modified Mar 19 2026 09:42:28, uncompressed size 20, method=store │ │ │ │ +Zip archive data, made by v6.1 UNIX, extract using at least v1.0, last modified Mar 19 2026 15:36:58, uncompressed size 20, method=store │ │ │ ├── OEBPS/vulnerabilities.xhtml │ │ │ │ @@ -48,69 +48,69 @@ │ │ │ │ vulnerability (affected), the affected Erlang/OTP releases, namely 28.0, │ │ │ │ 28.0.1, and 28.0.2, and the Erlang/OTP application that was vulnerable │ │ │ │ in application version ssh@5.3, ssh@5.3.1, and ssh@5.3.2. │ │ │ │ Erlang/OTP reports the affected versions using the release and the │ │ │ │ application versions because it is possible to update the application independently │ │ │ │ from the release. │ │ │ │ In some cases, there may be an optional action statement that describes a workaround │ │ │ │ -to avoid the mentioned vulnerability.

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

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

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

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

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

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

│ │ │ │ +},

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

│ │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

│ │ │ ├── OEBPS/typespec.xhtml │ │ │ │ @@ -143,126 +143,126 @@ │ │ │ │ and optional (=>) association types. If an association type is mandatory, an │ │ │ │ association with that type needs to be present. In the case of an optional │ │ │ │ association type it is not required for the key type to be present.

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

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

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

Table: Built-in types, predefined aliases

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

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

Table: Additional built-in types

Note

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

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

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

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

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

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

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

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

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

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

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

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

│ │ │ │

Change

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

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

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

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

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

-type gadget() :: #{}.

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

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

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

-type gadget() :: #{}.

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

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

│ │ │ │

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

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

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

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

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

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

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

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

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

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

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

Example:

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

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

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

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

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

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

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

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

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

Example:

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

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

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

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

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

Change

Nominal types were introduced in Erlang/OTP 28.

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

Read more on Opaques and Nominals

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

│ │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

Change

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

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

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

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

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

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

#rec{}

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

#rec{some_field :: Type}

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

Note

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

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

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

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

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

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

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

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

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

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

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

│ │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

-spec id(X) -> X.

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

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

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

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

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

Note

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Note

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

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

is equivalent to:

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

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

DO

ets:delete(Tab, Key),

DO NOT

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

DO

ets:delete(Tab, Key),

DO NOT

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

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

│ │ │ │

Do not fetch data that you already have.

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

Note

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

DO

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

DO NOT

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

DO NOT

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

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

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

│ │ │ │

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

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

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

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

DO

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

DO

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

DO NOT

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

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

DO

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

DO NOT

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

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

DO

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

DO NOT

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

DO NOT

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

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

DO

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

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

DO

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

DO NOT

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

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

DO NOT

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

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

│ │ │ │

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

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

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

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

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

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

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

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

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

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

│ │ │ │

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

Example:

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

Example:

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

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

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

│ │ │ │

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

Example:

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

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

│ │ │ │

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

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

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

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

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

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

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

│ │ │ │ -

This is the type definition for the supervisor flags:

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

This is the type definition for the supervisor flags:

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

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

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

│ │ │ │ -

The type definition for a child specification is as follows:

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

    The id key is mandatory.

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

    The type definition for a child specification is as follows:

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

      The id key is mandatory.

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

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

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

      The start key is mandatory.

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

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

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

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

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

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

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

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

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

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

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

      or simplified, relying on the default values:

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

      or simplified, relying on the default values:

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

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

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

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

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

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

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

      Example: A child specification to start another supervisor:

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

      Example: A child specification to start another supervisor:

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

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

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

      │ │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      │ │ │ │

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

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

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

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

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

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

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

      For example, adding a child to simple_sup above:

      supervisor:start_child(Pid, [id1])

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

      call:start_link(id1)

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

      supervisor:terminate_child(Sup, Pid)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      Depending on how your state machine is specified, this can be a very useful │ │ │ │ @@ -404,72 +404,72 @@ │ │ │ │ │ │ │ │ locked --> check_code : {button, Button}<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 store its length, and key buttons │ │ │ │ -that stores the collected buttons up to the same length.

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

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

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

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

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

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

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

      callback_mode() ->
      │ │ │ │      state_functions.

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

      │ │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      │ │ │ │

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

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

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

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

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

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

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

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

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

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

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

      │ │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

      │ │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

      │ │ │ │

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

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

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

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

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

      │ │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      │ │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      │ │ │ │ -

      Using state functions:

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

      Using state functions:

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

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

      │ │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      │ │ │ │

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

      Note

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

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

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

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

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

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

      │ │ │ │

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

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

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

      │ │ │ │ +8> sys:statistics(code_lock, get). │ │ │ │ +{ok,[{start_time,{{2024,5,3},{8,11,1}}}, │ │ │ │ + {current_time,{{2024,5,3},{8,11,48}}}, │ │ │ │ + {reductions,4098}, │ │ │ │ + {messages_in,5}, │ │ │ │ + {messages_out,0}]} │ │ │ │ +9> sys:statistics(code_lock, false). │ │ │ │ +ok │ │ │ │ +10> sys:trace(code_lock, false). │ │ │ │ +ok │ │ │ │ +11> sys:get_status(code_lock). │ │ │ │ +{status,<0.90.0>, │ │ │ │ + {module,gen_statem}, │ │ │ │ + [[{'$initial_call',{code_lock,init,1}}, │ │ │ │ + {'$ancestors',[<0.88.0>,<0.87.0>,<0.70.0>,<0.65.0>,<0.69.0>, │ │ │ │ + <0.64.0>,kernel_sup,<0.47.0>]}], │ │ │ │ + running,<0.88.0>,[], │ │ │ │ + [{header,"Status for state machine code_lock"}, │ │ │ │ + {data,[{"Status",running}, │ │ │ │ + {"Parent",<0.88.0>}, │ │ │ │ + {"Modules",[code_lock]}, │ │ │ │ + {"Time-outs",{0,[]}}, │ │ │ │ + {"Logged Events",[]}, │ │ │ │ + {"Postponed",[]}]}, │ │ │ │ + {data,[{"State", │ │ │ │ + {locked,#{code => [1,2,3,4], │ │ │ │ + length => 4,buttons => []}}}]}]]}

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

      │ │ │ │

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

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

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

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

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

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

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

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

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

      │ │ │ │ +6> sys:statistics(ch4, get). │ │ │ │ +{ok,[{start_time,{{2024,5,3},{8,26,13}}}, │ │ │ │ + {current_time,{{2024,5,3},{8,26,49}}}, │ │ │ │ + {reductions,202}, │ │ │ │ + {messages_in,2}, │ │ │ │ + {messages_out,1}]} │ │ │ │ +7> sys:statistics(ch4, false). │ │ │ │ +ok │ │ │ │ +8> sys:trace(ch4, false). │ │ │ │ +ok │ │ │ │ +9> sys:get_status(ch4). │ │ │ │ +{status,<0.90.0>, │ │ │ │ + {module,ch4}, │ │ │ │ + [[{'$initial_call',{ch4,init,1}}, │ │ │ │ + {'$ancestors',[<0.88.0>,<0.87.0>,<0.70.0>,<0.65.0>,<0.69.0>, │ │ │ │ + <0.64.0>,kernel_sup,<0.47.0>]}], │ │ │ │ + running,<0.88.0>,[], │ │ │ │ + {[1],[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19|...]}]}

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

      │ │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      │ │ │ │

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

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

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

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

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

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

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

      The arguments have the follow meaning:

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

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

        The arguments have the follow meaning:

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

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

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

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

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

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

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

        │ │ │ │

        System messages are received as:

        {system, From, Request}

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

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

        The arguments have the following meaning:

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

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

          The arguments have the following meaning:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

          Here is an example:

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

          Here is an example:

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

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

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

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

          Note

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

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

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

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

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

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

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

          Example:

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

          In a callback module:

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

          In a callback module:

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

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

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

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

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

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

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

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

          Here is a bit more complex calculation:

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

          Here is a bit more complex calculation:

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

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

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

          The following output is shown:

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

          Type a to leave the Erlang system.

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

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

          Type a to leave the Erlang system.

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

          3> halt().
          │ │ │ │  $

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

          │ │ │ │

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

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

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

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

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

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

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

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

          Now run the program:

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

          Now run the program:

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

          As expected, double of 10 is 20.

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

          -module(tut).

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

          -module(tut).

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

          4> tut:double(10).

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

          -export([double/1]).

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

          4> tut:double(10).

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

          -export([double/1]).

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

          Compile the file:

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

          And now calculate the factorial of 4.

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

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

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

          Compile the file:

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

          And now calculate the factorial of 4.

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

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

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

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

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

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

          Compile:

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

          Try out the new function mult:

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

          Compile:

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

          Try out the new function mult:

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

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

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

          │ │ │ │

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

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

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

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

          Compile:

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

          Test:

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

          Compile:

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

          Test:

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

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

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

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

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

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

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

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

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

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

          │ │ │ │ -

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

          tut2:convert(3, inch).

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

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

          tut2:convert(3, inch).

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

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

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

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

          Compile and test:

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

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

          Compile and test:

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

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

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

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

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

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

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

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

          │ │ │ │

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

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

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

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

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

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

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

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

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

          Another example:

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

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

          Another example:

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

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

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

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

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

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

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

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

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

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

          Compile and test:

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

          Explanation:

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

          The length of an empty list is obviously 0.

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

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

          Compile and test:

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

          Explanation:

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

          The length of an empty list is obviously 0.

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

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

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

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

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

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

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

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

          │ │ │ │

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

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

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

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

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

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

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

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

          Compile and test:

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

          This example warrants some explanation:

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

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

          Compile and test:

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

          This example warrants some explanation:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

          │ │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

          This is possibly a little clearer.

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

          This is possibly a little clearer.

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

          │ │ │ │ -

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

          Test the function:

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

          Explanation:

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

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

          Test the function:

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

          Explanation:

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

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

          [Converted_City | convert_list_to_c(Rest)];

          or:

          [City | convert_list_to_c(Rest)];

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

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

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

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

          [Converted_City | convert_list_to_c(Rest)];

          or:

          [City | convert_list_to_c(Rest)];

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

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

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

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

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

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

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

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

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

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

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

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

          Testing this program gives:

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

          Testing this program gives:

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

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

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

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

          The same program can also be written as:

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

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

          The same program can also be written as:

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

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

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

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

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

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

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

          │ │ │ │

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

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

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

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

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

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

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

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

          So instead of writing:

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

          So instead of writing:

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

          it can be written:

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

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

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

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

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

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

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

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

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

          │ │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

          lists:map(fun convert_to_c/1, List)

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

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

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

          lists:map(fun convert_to_c/1, List)

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

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

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

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

          In sort the fun is used:

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

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

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

          In sort the fun is used:

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

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

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

          Before improving the messenger program, let us look at some general principles, │ │ │ │ using the ping pong program as an example. Recall that when "ping" finishes, it │ │ │ │ tells "pong" that it has done so by sending the atom finished as a message to │ │ │ │ "pong" so that "pong" can also finish. Another way to let "pong" finish is to │ │ │ │ make "pong" exit if it does not receive a message from ping within a certain │ │ │ │ time. This can be done by adding a time-out to pong as shown in the │ │ │ │ -following example:

          -module(tut19).
          │ │ │ │ +following example:

          -module(tut19).
          │ │ │ │  
          │ │ │ │ --export([start_ping/1, start_pong/0,  ping/2, pong/0]).
          │ │ │ │ +-export([start_ping/1, start_pong/0,  ping/2, pong/0]).
          │ │ │ │  
          │ │ │ │ -ping(0, Pong_Node) ->
          │ │ │ │ -    io:format("ping finished~n", []);
          │ │ │ │ +ping(0, Pong_Node) ->
          │ │ │ │ +    io:format("ping finished~n", []);
          │ │ │ │  
          │ │ │ │ -ping(N, Pong_Node) ->
          │ │ │ │ -    {pong, Pong_Node} ! {ping, self()},
          │ │ │ │ +ping(N, Pong_Node) ->
          │ │ │ │ +    {pong, Pong_Node} ! {ping, self()},
          │ │ │ │      receive
          │ │ │ │          pong ->
          │ │ │ │ -            io:format("Ping received pong~n", [])
          │ │ │ │ +            io:format("Ping received pong~n", [])
          │ │ │ │      end,
          │ │ │ │ -    ping(N - 1, Pong_Node).
          │ │ │ │ +    ping(N - 1, Pong_Node).
          │ │ │ │  
          │ │ │ │ -pong() ->
          │ │ │ │ +pong() ->
          │ │ │ │      receive
          │ │ │ │ -        {ping, Ping_PID} ->
          │ │ │ │ -            io:format("Pong received ping~n", []),
          │ │ │ │ +        {ping, Ping_PID} ->
          │ │ │ │ +            io:format("Pong received ping~n", []),
          │ │ │ │              Ping_PID ! pong,
          │ │ │ │ -            pong()
          │ │ │ │ +            pong()
          │ │ │ │      after 5000 ->
          │ │ │ │ -            io:format("Pong timed out~n", [])
          │ │ │ │ +            io:format("Pong timed out~n", [])
          │ │ │ │      end.
          │ │ │ │  
          │ │ │ │ -start_pong() ->
          │ │ │ │ -    register(pong, spawn(tut19, pong, [])).
          │ │ │ │ +start_pong() ->
          │ │ │ │ +    register(pong, spawn(tut19, pong, [])).
          │ │ │ │  
          │ │ │ │ -start_ping(Pong_Node) ->
          │ │ │ │ -    spawn(tut19, ping, [3, Pong_Node]).

          After this is compiled and the file tut19.beam is copied to the necessary │ │ │ │ +start_ping(Pong_Node) -> │ │ │ │ + spawn(tut19, ping, [3, Pong_Node]).

          After this is compiled and the file tut19.beam is copied to the necessary │ │ │ │ directories, the following is seen on (pong@kosken):

          (pong@kosken)1> tut19:start_pong().
          │ │ │ │  true
          │ │ │ │  Pong received ping
          │ │ │ │  Pong received ping
          │ │ │ │  Pong received ping
          │ │ │ │  Pong timed out

          And the following is seen on (ping@gollum):

          (ping@gollum)1> tut19:start_ping(pong@kosken).
          │ │ │ │  <0.36.0>
          │ │ │ │  Ping received pong
          │ │ │ │  Ping received pong
          │ │ │ │  Ping received pong
          │ │ │ │ -ping finished

          The time-out is set in:

          pong() ->
          │ │ │ │ +ping finished

          The time-out is set in:

          pong() ->
          │ │ │ │      receive
          │ │ │ │ -        {ping, Ping_PID} ->
          │ │ │ │ -            io:format("Pong received ping~n", []),
          │ │ │ │ +        {ping, Ping_PID} ->
          │ │ │ │ +            io:format("Pong received ping~n", []),
          │ │ │ │              Ping_PID ! pong,
          │ │ │ │ -            pong()
          │ │ │ │ +            pong()
          │ │ │ │      after 5000 ->
          │ │ │ │ -            io:format("Pong timed out~n", [])
          │ │ │ │ +            io:format("Pong timed out~n", [])
          │ │ │ │      end.

          The time-out (after 5000) is started when receive is entered. The time-out │ │ │ │ is canceled if {ping,Ping_PID} is received. If {ping,Ping_PID} is not │ │ │ │ received, the actions following the time-out are done after 5000 milliseconds. │ │ │ │ after must be last in the receive, that is, preceded by all other message │ │ │ │ reception specifications in the receive. It is also possible to call a │ │ │ │ -function that returned an integer for the time-out:

          after pong_timeout() ->

          In general, there are better ways than using time-outs to supervise parts of a │ │ │ │ +function that returned an integer for the time-out:

          after pong_timeout() ->

          In general, there are better ways than using time-outs to supervise parts of a │ │ │ │ distributed Erlang system. Time-outs are usually appropriate to supervise │ │ │ │ external events, for example, if you have expected a message from some external │ │ │ │ system within a specified time. For example, a time-out can be used to log a │ │ │ │ user out of the messenger system if they have not accessed it for, say, ten │ │ │ │ minutes.

          │ │ │ │ │ │ │ │ │ │ │ │ @@ -114,96 +114,96 @@ │ │ │ │ something called a signal to all the processes it has links to.

          The signal carries information about the pid it was sent from and the exit │ │ │ │ reason.

          The default behaviour of a process that receives a normal exit is to ignore the │ │ │ │ signal.

          The default behaviour in the two other cases (that is, abnormal exit) above is │ │ │ │ to:

          • Bypass all messages to the receiving process.
          • Kill the receiving process.
          • Propagate the same error signal to the links of the killed process.

          In this way you can connect all processes in a transaction together using links. │ │ │ │ If one of the processes exits abnormally, all the processes in the transaction │ │ │ │ are killed. As it is often wanted to create a process and link to it at the same │ │ │ │ time, there is a special BIF, spawn_link that does the │ │ │ │ -same as spawn, but also creates a link to the spawned process.

          Now an example of the ping pong example using links to terminate "pong":

          -module(tut20).
          │ │ │ │ +same as spawn, but also creates a link to the spawned process.

          Now an example of the ping pong example using links to terminate "pong":

          -module(tut20).
          │ │ │ │  
          │ │ │ │ --export([start/1,  ping/2, pong/0]).
          │ │ │ │ +-export([start/1,  ping/2, pong/0]).
          │ │ │ │  
          │ │ │ │ -ping(N, Pong_Pid) ->
          │ │ │ │ -    link(Pong_Pid),
          │ │ │ │ -    ping1(N, Pong_Pid).
          │ │ │ │ +ping(N, Pong_Pid) ->
          │ │ │ │ +    link(Pong_Pid),
          │ │ │ │ +    ping1(N, Pong_Pid).
          │ │ │ │  
          │ │ │ │ -ping1(0, _) ->
          │ │ │ │ -    exit(ping);
          │ │ │ │ +ping1(0, _) ->
          │ │ │ │ +    exit(ping);
          │ │ │ │  
          │ │ │ │ -ping1(N, Pong_Pid) ->
          │ │ │ │ -    Pong_Pid ! {ping, self()},
          │ │ │ │ +ping1(N, Pong_Pid) ->
          │ │ │ │ +    Pong_Pid ! {ping, self()},
          │ │ │ │      receive
          │ │ │ │          pong ->
          │ │ │ │ -            io:format("Ping received pong~n", [])
          │ │ │ │ +            io:format("Ping received pong~n", [])
          │ │ │ │      end,
          │ │ │ │ -    ping1(N - 1, Pong_Pid).
          │ │ │ │ +    ping1(N - 1, Pong_Pid).
          │ │ │ │  
          │ │ │ │ -pong() ->
          │ │ │ │ +pong() ->
          │ │ │ │      receive
          │ │ │ │ -        {ping, Ping_PID} ->
          │ │ │ │ -            io:format("Pong received ping~n", []),
          │ │ │ │ +        {ping, Ping_PID} ->
          │ │ │ │ +            io:format("Pong received ping~n", []),
          │ │ │ │              Ping_PID ! pong,
          │ │ │ │ -            pong()
          │ │ │ │ +            pong()
          │ │ │ │      end.
          │ │ │ │  
          │ │ │ │ -start(Ping_Node) ->
          │ │ │ │ -    PongPID = spawn(tut20, pong, []),
          │ │ │ │ -    spawn(Ping_Node, tut20, ping, [3, PongPID]).
          (s1@bill)3> tut20:start(s2@kosken).
          │ │ │ │ +start(Ping_Node) ->
          │ │ │ │ +    PongPID = spawn(tut20, pong, []),
          │ │ │ │ +    spawn(Ping_Node, tut20, ping, [3, PongPID]).
          (s1@bill)3> tut20:start(s2@kosken).
          │ │ │ │  Pong received ping
          │ │ │ │  <3820.41.0>
          │ │ │ │  Ping received pong
          │ │ │ │  Pong received ping
          │ │ │ │  Ping received pong
          │ │ │ │  Pong received ping
          │ │ │ │  Ping received pong

          This is a slight modification of the ping pong program where both processes are │ │ │ │ spawned from the same start/1 function, and the "ping" process can be spawned │ │ │ │ on a separate node. Notice the use of the link BIF. "Ping" calls │ │ │ │ exit(ping) when it finishes and this causes an exit signal to be │ │ │ │ sent to "pong", which also terminates.

          It is possible to modify the default behaviour of a process so that it does not │ │ │ │ get killed when it receives abnormal exit signals. Instead, all signals are │ │ │ │ turned into normal messages on the format {'EXIT',FromPID,Reason} and added to │ │ │ │ -the end of the receiving process' message queue. This behaviour is set by:

          process_flag(trap_exit, true)

          There are several other process flags, see erlang(3). │ │ │ │ +the end of the receiving process' message queue. This behaviour is set by:

          process_flag(trap_exit, true)

          There are several other process flags, see erlang(3). │ │ │ │ Changing the default behaviour of a process in this way is usually not done in │ │ │ │ standard user programs, but is left to the supervisory programs in OTP. However, │ │ │ │ -the ping pong program is modified to illustrate exit trapping.

          -module(tut21).
          │ │ │ │ +the ping pong program is modified to illustrate exit trapping.

          -module(tut21).
          │ │ │ │  
          │ │ │ │ --export([start/1,  ping/2, pong/0]).
          │ │ │ │ +-export([start/1,  ping/2, pong/0]).
          │ │ │ │  
          │ │ │ │ -ping(N, Pong_Pid) ->
          │ │ │ │ -    link(Pong_Pid),
          │ │ │ │ -    ping1(N, Pong_Pid).
          │ │ │ │ +ping(N, Pong_Pid) ->
          │ │ │ │ +    link(Pong_Pid),
          │ │ │ │ +    ping1(N, Pong_Pid).
          │ │ │ │  
          │ │ │ │ -ping1(0, _) ->
          │ │ │ │ -    exit(ping);
          │ │ │ │ +ping1(0, _) ->
          │ │ │ │ +    exit(ping);
          │ │ │ │  
          │ │ │ │ -ping1(N, Pong_Pid) ->
          │ │ │ │ -    Pong_Pid ! {ping, self()},
          │ │ │ │ +ping1(N, Pong_Pid) ->
          │ │ │ │ +    Pong_Pid ! {ping, self()},
          │ │ │ │      receive
          │ │ │ │          pong ->
          │ │ │ │ -            io:format("Ping received pong~n", [])
          │ │ │ │ +            io:format("Ping received pong~n", [])
          │ │ │ │      end,
          │ │ │ │ -    ping1(N - 1, Pong_Pid).
          │ │ │ │ +    ping1(N - 1, Pong_Pid).
          │ │ │ │  
          │ │ │ │ -pong() ->
          │ │ │ │ -    process_flag(trap_exit, true),
          │ │ │ │ -    pong1().
          │ │ │ │ +pong() ->
          │ │ │ │ +    process_flag(trap_exit, true),
          │ │ │ │ +    pong1().
          │ │ │ │  
          │ │ │ │ -pong1() ->
          │ │ │ │ +pong1() ->
          │ │ │ │      receive
          │ │ │ │ -        {ping, Ping_PID} ->
          │ │ │ │ -            io:format("Pong received ping~n", []),
          │ │ │ │ +        {ping, Ping_PID} ->
          │ │ │ │ +            io:format("Pong received ping~n", []),
          │ │ │ │              Ping_PID ! pong,
          │ │ │ │ -            pong1();
          │ │ │ │ -        {'EXIT', From, Reason} ->
          │ │ │ │ -            io:format("pong exiting, got ~p~n", [{'EXIT', From, Reason}])
          │ │ │ │ +            pong1();
          │ │ │ │ +        {'EXIT', From, Reason} ->
          │ │ │ │ +            io:format("pong exiting, got ~p~n", [{'EXIT', From, Reason}])
          │ │ │ │      end.
          │ │ │ │  
          │ │ │ │ -start(Ping_Node) ->
          │ │ │ │ -    PongPID = spawn(tut21, pong, []),
          │ │ │ │ -    spawn(Ping_Node, tut21, ping, [3, PongPID]).
          (s1@bill)1> tut21:start(s2@gollum).
          │ │ │ │ +start(Ping_Node) ->
          │ │ │ │ +    PongPID = spawn(tut21, pong, []),
          │ │ │ │ +    spawn(Ping_Node, tut21, ping, [3, PongPID]).
          (s1@bill)1> tut21:start(s2@gollum).
          │ │ │ │  <3820.39.0>
          │ │ │ │  Pong received ping
          │ │ │ │  Ping received pong
          │ │ │ │  Pong received ping
          │ │ │ │  Ping received pong
          │ │ │ │  Pong received ping
          │ │ │ │  Ping received pong
          │ │ │ │ @@ -256,135 +256,135 @@
          │ │ │ │  %%% Started: messenger:client(Server_Node, Name)
          │ │ │ │  %%% To client: logoff
          │ │ │ │  %%% To client: {message_to, ToName, Message}
          │ │ │ │  %%%
          │ │ │ │  %%% Configuration: change the server_node() function to return the
          │ │ │ │  %%% name of the node where the messenger server runs
          │ │ │ │  
          │ │ │ │ --module(messenger).
          │ │ │ │ --export([start_server/0, server/0,
          │ │ │ │ -         logon/1, logoff/0, message/2, client/2]).
          │ │ │ │ +-module(messenger).
          │ │ │ │ +-export([start_server/0, server/0,
          │ │ │ │ +         logon/1, logoff/0, message/2, client/2]).
          │ │ │ │  
          │ │ │ │  %%% Change the function below to return the name of the node where the
          │ │ │ │  %%% messenger server runs
          │ │ │ │ -server_node() ->
          │ │ │ │ +server_node() ->
          │ │ │ │      messenger@super.
          │ │ │ │  
          │ │ │ │  %%% This is the server process for the "messenger"
          │ │ │ │  %%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
          │ │ │ │ -server() ->
          │ │ │ │ -    process_flag(trap_exit, true),
          │ │ │ │ -    server([]).
          │ │ │ │ +server() ->
          │ │ │ │ +    process_flag(trap_exit, true),
          │ │ │ │ +    server([]).
          │ │ │ │  
          │ │ │ │ -server(User_List) ->
          │ │ │ │ +server(User_List) ->
          │ │ │ │      receive
          │ │ │ │ -        {From, logon, Name} ->
          │ │ │ │ -            New_User_List = server_logon(From, Name, User_List),
          │ │ │ │ -            server(New_User_List);
          │ │ │ │ -        {'EXIT', From, _} ->
          │ │ │ │ -            New_User_List = server_logoff(From, User_List),
          │ │ │ │ -            server(New_User_List);
          │ │ │ │ -        {From, message_to, To, Message} ->
          │ │ │ │ -            server_transfer(From, To, Message, User_List),
          │ │ │ │ -            io:format("list is now: ~p~n", [User_List]),
          │ │ │ │ -            server(User_List)
          │ │ │ │ +        {From, logon, Name} ->
          │ │ │ │ +            New_User_List = server_logon(From, Name, User_List),
          │ │ │ │ +            server(New_User_List);
          │ │ │ │ +        {'EXIT', From, _} ->
          │ │ │ │ +            New_User_List = server_logoff(From, User_List),
          │ │ │ │ +            server(New_User_List);
          │ │ │ │ +        {From, message_to, To, Message} ->
          │ │ │ │ +            server_transfer(From, To, Message, User_List),
          │ │ │ │ +            io:format("list is now: ~p~n", [User_List]),
          │ │ │ │ +            server(User_List)
          │ │ │ │      end.
          │ │ │ │  
          │ │ │ │  %%% Start the server
          │ │ │ │ -start_server() ->
          │ │ │ │ -    register(messenger, spawn(messenger, server, [])).
          │ │ │ │ +start_server() ->
          │ │ │ │ +    register(messenger, spawn(messenger, server, [])).
          │ │ │ │  
          │ │ │ │  %%% Server adds a new user to the user list
          │ │ │ │ -server_logon(From, Name, User_List) ->
          │ │ │ │ +server_logon(From, Name, User_List) ->
          │ │ │ │      %% check if logged on anywhere else
          │ │ │ │ -    case lists:keymember(Name, 2, User_List) of
          │ │ │ │ +    case lists:keymember(Name, 2, User_List) of
          │ │ │ │          true ->
          │ │ │ │ -            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
          │ │ │ │ +            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
          │ │ │ │              User_List;
          │ │ │ │          false ->
          │ │ │ │ -            From ! {messenger, logged_on},
          │ │ │ │ -            link(From),
          │ │ │ │ -            [{From, Name} | User_List]        %add user to the list
          │ │ │ │ +            From ! {messenger, logged_on},
          │ │ │ │ +            link(From),
          │ │ │ │ +            [{From, Name} | User_List]        %add user to the list
          │ │ │ │      end.
          │ │ │ │  
          │ │ │ │  %%% Server deletes a user from the user list
          │ │ │ │ -server_logoff(From, User_List) ->
          │ │ │ │ -    lists:keydelete(From, 1, User_List).
          │ │ │ │ +server_logoff(From, User_List) ->
          │ │ │ │ +    lists:keydelete(From, 1, User_List).
          │ │ │ │  
          │ │ │ │  
          │ │ │ │  %%% Server transfers a message between user
          │ │ │ │ -server_transfer(From, To, Message, User_List) ->
          │ │ │ │ +server_transfer(From, To, Message, User_List) ->
          │ │ │ │      %% check that the user is logged on and who he is
          │ │ │ │ -    case lists:keysearch(From, 1, User_List) of
          │ │ │ │ +    case lists:keysearch(From, 1, User_List) of
          │ │ │ │          false ->
          │ │ │ │ -            From ! {messenger, stop, you_are_not_logged_on};
          │ │ │ │ -        {value, {_, Name}} ->
          │ │ │ │ -            server_transfer(From, Name, To, Message, User_List)
          │ │ │ │ +            From ! {messenger, stop, you_are_not_logged_on};
          │ │ │ │ +        {value, {_, Name}} ->
          │ │ │ │ +            server_transfer(From, Name, To, Message, User_List)
          │ │ │ │      end.
          │ │ │ │  
          │ │ │ │  %%% If the user exists, send the message
          │ │ │ │ -server_transfer(From, Name, To, Message, User_List) ->
          │ │ │ │ +server_transfer(From, Name, To, Message, User_List) ->
          │ │ │ │      %% Find the receiver and send the message
          │ │ │ │ -    case lists:keysearch(To, 2, User_List) of
          │ │ │ │ +    case lists:keysearch(To, 2, User_List) of
          │ │ │ │          false ->
          │ │ │ │ -            From ! {messenger, receiver_not_found};
          │ │ │ │ -        {value, {ToPid, To}} ->
          │ │ │ │ -            ToPid ! {message_from, Name, Message},
          │ │ │ │ -            From ! {messenger, sent}
          │ │ │ │ +            From ! {messenger, receiver_not_found};
          │ │ │ │ +        {value, {ToPid, To}} ->
          │ │ │ │ +            ToPid ! {message_from, Name, Message},
          │ │ │ │ +            From ! {messenger, sent}
          │ │ │ │      end.
          │ │ │ │  
          │ │ │ │  %%% User Commands
          │ │ │ │ -logon(Name) ->
          │ │ │ │ -    case whereis(mess_client) of
          │ │ │ │ +logon(Name) ->
          │ │ │ │ +    case whereis(mess_client) of
          │ │ │ │          undefined ->
          │ │ │ │ -            register(mess_client,
          │ │ │ │ -                     spawn(messenger, client, [server_node(), Name]));
          │ │ │ │ +            register(mess_client,
          │ │ │ │ +                     spawn(messenger, client, [server_node(), Name]));
          │ │ │ │          _ -> already_logged_on
          │ │ │ │      end.
          │ │ │ │  
          │ │ │ │ -logoff() ->
          │ │ │ │ +logoff() ->
          │ │ │ │      mess_client ! logoff.
          │ │ │ │  
          │ │ │ │ -message(ToName, Message) ->
          │ │ │ │ -    case whereis(mess_client) of % Test if the client is running
          │ │ │ │ +message(ToName, Message) ->
          │ │ │ │ +    case whereis(mess_client) of % Test if the client is running
          │ │ │ │          undefined ->
          │ │ │ │              not_logged_on;
          │ │ │ │ -        _ -> mess_client ! {message_to, ToName, Message},
          │ │ │ │ +        _ -> mess_client ! {message_to, ToName, Message},
          │ │ │ │               ok
          │ │ │ │  end.
          │ │ │ │  
          │ │ │ │  %%% The client process which runs on each user node
          │ │ │ │ -client(Server_Node, Name) ->
          │ │ │ │ -    {messenger, Server_Node} ! {self(), logon, Name},
          │ │ │ │ -    await_result(),
          │ │ │ │ -    client(Server_Node).
          │ │ │ │ +client(Server_Node, Name) ->
          │ │ │ │ +    {messenger, Server_Node} ! {self(), logon, Name},
          │ │ │ │ +    await_result(),
          │ │ │ │ +    client(Server_Node).
          │ │ │ │  
          │ │ │ │ -client(Server_Node) ->
          │ │ │ │ +client(Server_Node) ->
          │ │ │ │      receive
          │ │ │ │          logoff ->
          │ │ │ │ -            exit(normal);
          │ │ │ │ -        {message_to, ToName, Message} ->
          │ │ │ │ -            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
          │ │ │ │ -            await_result();
          │ │ │ │ -        {message_from, FromName, Message} ->
          │ │ │ │ -            io:format("Message from ~p: ~p~n", [FromName, Message])
          │ │ │ │ +            exit(normal);
          │ │ │ │ +        {message_to, ToName, Message} ->
          │ │ │ │ +            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
          │ │ │ │ +            await_result();
          │ │ │ │ +        {message_from, FromName, Message} ->
          │ │ │ │ +            io:format("Message from ~p: ~p~n", [FromName, Message])
          │ │ │ │      end,
          │ │ │ │ -    client(Server_Node).
          │ │ │ │ +    client(Server_Node).
          │ │ │ │  
          │ │ │ │  %%% wait for a response from the server
          │ │ │ │ -await_result() ->
          │ │ │ │ +await_result() ->
          │ │ │ │      receive
          │ │ │ │ -        {messenger, stop, Why} -> % Stop the client
          │ │ │ │ -            io:format("~p~n", [Why]),
          │ │ │ │ -            exit(normal);
          │ │ │ │ -        {messenger, What} ->  % Normal response
          │ │ │ │ -            io:format("~p~n", [What])
          │ │ │ │ +        {messenger, stop, Why} -> % Stop the client
          │ │ │ │ +            io:format("~p~n", [Why]),
          │ │ │ │ +            exit(normal);
          │ │ │ │ +        {messenger, What} ->  % Normal response
          │ │ │ │ +            io:format("~p~n", [What])
          │ │ │ │      after 5000 ->
          │ │ │ │ -            io:format("No response from server~n", []),
          │ │ │ │ -            exit(timeout)
          │ │ │ │ +            io:format("No response from server~n", []),
          │ │ │ │ +            exit(timeout)
          │ │ │ │      end.

          The following changes are added:

          The messenger server traps exits. If it receives an exit signal, │ │ │ │ {'EXIT',From,Reason}, this means that a client process has terminated or is │ │ │ │ unreachable for one of the following reasons:

          • The user has logged off (the "logoff" message is removed).
          • The network connection to the client is broken.
          • The node on which the client process resides has gone down.
          • The client processes has done some illegal operation.

          If an exit signal is received as above, the tuple {From,Name} is deleted from │ │ │ │ the servers User_List using the server_logoff function. If the node on which │ │ │ │ the server runs goes down, an exit signal (automatically generated by the │ │ │ │ system) is sent to all of the client processes: │ │ │ │ {'EXIT',MessengerPID,noconnection} causing all the client processes to │ │ │ ├── OEBPS/release_structure.xhtml │ │ │ │ @@ -41,37 +41,37 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Release Resource File │ │ │ │

          │ │ │ │

          To define a release, create a release resource file, or in short a .rel │ │ │ │ file. In the file, specify the name and version of the release, which ERTS │ │ │ │ -version it is based on, and which applications it consists of:

          {release, {Name,Vsn}, {erts, EVsn},
          │ │ │ │ - [{Application1, AppVsn1},
          │ │ │ │ +version it is based on, and which applications it consists of:

          {release, {Name,Vsn}, {erts, EVsn},
          │ │ │ │ + [{Application1, AppVsn1},
          │ │ │ │     ...
          │ │ │ │ -  {ApplicationN, AppVsnN}]}.

          Name, Vsn, EVsn, and AppVsn are strings.

          The file must be named Rel.rel, where Rel is a unique name.

          Each Application (atom) and AppVsn is the name and version of an application │ │ │ │ + {ApplicationN, AppVsnN}]}.

          Name, Vsn, EVsn, and AppVsn are strings.

          The file must be named Rel.rel, where Rel is a unique name.

          Each Application (atom) and AppVsn is the name and version of an application │ │ │ │ included in the release. The minimal release based on Erlang/OTP consists of the │ │ │ │ Kernel and STDLIB applications, so these applications must be included in the │ │ │ │ list.

          If the release is to be upgraded, it must also include the SASL application.

          Here is an example showing the .app file for a release of ch_app from │ │ │ │ -the Applications section:

          {application, ch_app,
          │ │ │ │ - [{description, "Channel allocator"},
          │ │ │ │ -  {vsn, "1"},
          │ │ │ │ -  {modules, [ch_app, ch_sup, ch3]},
          │ │ │ │ -  {registered, [ch3]},
          │ │ │ │ -  {applications, [kernel, stdlib, sasl]},
          │ │ │ │ -  {mod, {ch_app,[]}}
          │ │ │ │ - ]}.

          The .rel file must also contain kernel, stdlib, and sasl, as these │ │ │ │ -applications are required by ch_app. The file is called ch_rel-1.rel:

          {release,
          │ │ │ │ - {"ch_rel", "A"},
          │ │ │ │ - {erts, "14.2.5"},
          │ │ │ │ - [{kernel, "9.2.4"},
          │ │ │ │ -  {stdlib, "5.2.3"},
          │ │ │ │ -  {sasl, "4.2.1"},
          │ │ │ │ -  {ch_app, "1"}]
          │ │ │ │ -}.

          │ │ │ │ +the Applications section:

          {application, ch_app,
          │ │ │ │ + [{description, "Channel allocator"},
          │ │ │ │ +  {vsn, "1"},
          │ │ │ │ +  {modules, [ch_app, ch_sup, ch3]},
          │ │ │ │ +  {registered, [ch3]},
          │ │ │ │ +  {applications, [kernel, stdlib, sasl]},
          │ │ │ │ +  {mod, {ch_app,[]}}
          │ │ │ │ + ]}.

          The .rel file must also contain kernel, stdlib, and sasl, as these │ │ │ │ +applications are required by ch_app. The file is called ch_rel-1.rel:

          {release,
          │ │ │ │ + {"ch_rel", "A"},
          │ │ │ │ + {erts, "14.2.5"},
          │ │ │ │ + [{kernel, "9.2.4"},
          │ │ │ │ +  {stdlib, "5.2.3"},
          │ │ │ │ +  {sasl, "4.2.1"},
          │ │ │ │ +  {ch_app, "1"}]
          │ │ │ │ +}.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Generating Boot Scripts │ │ │ │

          │ │ │ │

          systools in the SASL application includes tools to build and check │ │ │ │ releases. The functions read the .rel and .app files and perform │ │ │ │ @@ -95,17 +95,17 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Creating a Release Package │ │ │ │ │ │ │ │

          The systools:make_tar/1,2 function takes a │ │ │ │ .rel file as input and creates a zipped tar file with the code for │ │ │ │ -the specified applications, a release package:

          1> systools:make_script("ch_rel-1").
          │ │ │ │ +the specified applications, a release package:

          1> systools:make_script("ch_rel-1").
          │ │ │ │  ok
          │ │ │ │ -2> systools:make_tar("ch_rel-1").
          │ │ │ │ +2> systools:make_tar("ch_rel-1").
          │ │ │ │  ok

          The release package by default contains:

          • The .app files
          • The .rel file
          • The object code for all applications, structured according to the │ │ │ │ application directory structure
          • The binary boot script renamed to start.boot
          % tar tf ch_rel-1.tar
          │ │ │ │  lib/kernel-9.2.4/ebin/kernel.app
          │ │ │ │  lib/kernel-9.2.4/ebin/application.beam
          │ │ │ │  ...
          │ │ │ │  lib/stdlib-5.2.3/ebin/stdlib.app
          │ │ │ │  lib/stdlib-5.2.3/ebin/argparse.beam
          │ │ │ ├── OEBPS/release_handling.xhtml
          │ │ │ │ @@ -128,38 +128,38 @@
          │ │ │ │    update
          │ │ │ │  
          │ │ │ │  

          If a more complex change has been made, for example, a change to the format of │ │ │ │ the internal state of a gen_server, simple code replacement is not sufficient. │ │ │ │ Instead, it is necessary to:

          • Suspend the processes using the module (to avoid that they try to handle any │ │ │ │ requests before the code replacement is completed).
          • Ask them to transform the internal state format and switch to the new version │ │ │ │ of the module.
          • Remove the old version.
          • Resume the processes.

          This is called synchronized code replacement and for this the following │ │ │ │ -instructions are used:

          {update, Module, {advanced, Extra}}
          │ │ │ │ -{update, Module, supervisor}

          update with argument {advanced,Extra} is used when changing the internal │ │ │ │ +instructions are used:

          {update, Module, {advanced, Extra}}
          │ │ │ │ +{update, Module, supervisor}

          update with argument {advanced,Extra} is used when changing the internal │ │ │ │ state of a behaviour as described above. It causes behaviour processes to call │ │ │ │ the callback function code_change/3, passing the term Extra and some other │ │ │ │ information as arguments. See the manual pages for the respective behaviours and │ │ │ │ Appup Cookbook.

          update with argument supervisor is used when changing the start │ │ │ │ specification of a supervisor. See Appup Cookbook.

          When a module is to be updated, the release handler finds which processes that │ │ │ │ are using the module by traversing the supervision tree of each running │ │ │ │ -application and checking all the child specifications:

          {Id, StartFunc, Restart, Shutdown, Type, Modules}

          A process uses a module if the name is listed in Modules in the child │ │ │ │ +application and checking all the child specifications:

          {Id, StartFunc, Restart, Shutdown, Type, Modules}

          A process uses a module if the name is listed in Modules in the child │ │ │ │ specification for the process.

          If Modules=dynamic, which is the case for event managers, the event manager │ │ │ │ process informs the release handler about the list of currently installed event │ │ │ │ handlers (gen_event), and it is checked if the module name is in this list │ │ │ │ instead.

          The release handler suspends, asks for code change, and resumes processes by │ │ │ │ calling the functions sys:suspend/1,2, sys:change_code/4,5, and │ │ │ │ sys:resume/1,2, respectively.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ add_module and delete_module │ │ │ │

          │ │ │ │ -

          If a new module is introduced, the following instruction is used:

          {add_module, Module}

          This instruction loads module Module. When running Erlang in │ │ │ │ +

          If a new module is introduced, the following instruction is used:

          {add_module, Module}

          This instruction loads module Module. When running Erlang in │ │ │ │ embedded mode it is necessary to use this this instruction. It is not │ │ │ │ strictly required when running Erlang in interactive mode, since the │ │ │ │ -code server automatically searches for and loads unloaded modules.

          The opposite of add_module is delete_module, which unloads a module:

          {delete_module, Module}

          Any process, in any application, with Module as residence module, is │ │ │ │ +code server automatically searches for and loads unloaded modules.

          The opposite of add_module is delete_module, which unloads a module:

          {delete_module, Module}

          Any process, in any application, with Module as residence module, is │ │ │ │ killed when the instruction is evaluated. Therefore, the user must │ │ │ │ ensure that all such processes are terminated before deleting module │ │ │ │ Module to avoid a situation with failing supervisor restarts.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Application Instructions │ │ │ │ @@ -246,60 +246,60 @@ │ │ │ │ .app file.

        • Each UpFromVsn is a previous version of the application to upgrade from.
        • Each DownToVsn is a previous version of the application to downgrade to.
        • Each Instructions is a list of release handling instructions.

        UpFromVsn and DownToVsn can also be specified as regular expressions. For │ │ │ │ more information about the syntax and contents of the .appup file, see │ │ │ │ appup in SASL.

        Appup Cookbook includes examples of .appup files for │ │ │ │ typical upgrade/downgrade cases.

        Example: Consider the release ch_rel-1 from │ │ │ │ Releases. Assume you want to add a function │ │ │ │ available/0 to server ch3, which returns the number of available channels │ │ │ │ (when trying out the example, make the change in a copy of the original │ │ │ │ -directory, to ensure that the first version is still available):

        -module(ch3).
        │ │ │ │ --behaviour(gen_server).
        │ │ │ │ +directory, to ensure that the first version is still available):

        -module(ch3).
        │ │ │ │ +-behaviour(gen_server).
        │ │ │ │  
        │ │ │ │ --export([start_link/0]).
        │ │ │ │ --export([alloc/0, free/1]).
        │ │ │ │ --export([available/0]).
        │ │ │ │ --export([init/1, handle_call/3, handle_cast/2]).
        │ │ │ │ +-export([start_link/0]).
        │ │ │ │ +-export([alloc/0, free/1]).
        │ │ │ │ +-export([available/0]).
        │ │ │ │ +-export([init/1, handle_call/3, handle_cast/2]).
        │ │ │ │  
        │ │ │ │ -start_link() ->
        │ │ │ │ -    gen_server:start_link({local, ch3}, ch3, [], []).
        │ │ │ │ +start_link() ->
        │ │ │ │ +    gen_server:start_link({local, ch3}, ch3, [], []).
        │ │ │ │  
        │ │ │ │ -alloc() ->
        │ │ │ │ -    gen_server:call(ch3, alloc).
        │ │ │ │ +alloc() ->
        │ │ │ │ +    gen_server:call(ch3, alloc).
        │ │ │ │  
        │ │ │ │ -free(Ch) ->
        │ │ │ │ -    gen_server:cast(ch3, {free, Ch}).
        │ │ │ │ +free(Ch) ->
        │ │ │ │ +    gen_server:cast(ch3, {free, Ch}).
        │ │ │ │  
        │ │ │ │ -available() ->
        │ │ │ │ -    gen_server:call(ch3, available).
        │ │ │ │ +available() ->
        │ │ │ │ +    gen_server:call(ch3, available).
        │ │ │ │  
        │ │ │ │ -init(_Args) ->
        │ │ │ │ -    {ok, channels()}.
        │ │ │ │ +init(_Args) ->
        │ │ │ │ +    {ok, channels()}.
        │ │ │ │  
        │ │ │ │ -handle_call(alloc, _From, Chs) ->
        │ │ │ │ -    {Ch, Chs2} = alloc(Chs),
        │ │ │ │ -    {reply, Ch, Chs2};
        │ │ │ │ -handle_call(available, _From, Chs) ->
        │ │ │ │ -    N = available(Chs),
        │ │ │ │ -    {reply, N, Chs}.
        │ │ │ │ +handle_call(alloc, _From, Chs) ->
        │ │ │ │ +    {Ch, Chs2} = alloc(Chs),
        │ │ │ │ +    {reply, Ch, Chs2};
        │ │ │ │ +handle_call(available, _From, Chs) ->
        │ │ │ │ +    N = available(Chs),
        │ │ │ │ +    {reply, N, Chs}.
        │ │ │ │  
        │ │ │ │ -handle_cast({free, Ch}, Chs) ->
        │ │ │ │ -    Chs2 = free(Ch, Chs),
        │ │ │ │ -    {noreply, Chs2}.

        A new version of the ch_app.app file must now be created, where the version is │ │ │ │ -updated:

        {application, ch_app,
        │ │ │ │ - [{description, "Channel allocator"},
        │ │ │ │ -  {vsn, "2"},
        │ │ │ │ -  {modules, [ch_app, ch_sup, ch3]},
        │ │ │ │ -  {registered, [ch3]},
        │ │ │ │ -  {applications, [kernel, stdlib, sasl]},
        │ │ │ │ -  {mod, {ch_app,[]}}
        │ │ │ │ - ]}.

        To upgrade ch_app from "1" to "2" (and to downgrade from "2" to "1"), │ │ │ │ +handle_cast({free, Ch}, Chs) -> │ │ │ │ + Chs2 = free(Ch, Chs), │ │ │ │ + {noreply, Chs2}.

        A new version of the ch_app.app file must now be created, where the version is │ │ │ │ +updated:

        {application, ch_app,
        │ │ │ │ + [{description, "Channel allocator"},
        │ │ │ │ +  {vsn, "2"},
        │ │ │ │ +  {modules, [ch_app, ch_sup, ch3]},
        │ │ │ │ +  {registered, [ch3]},
        │ │ │ │ +  {applications, [kernel, stdlib, sasl]},
        │ │ │ │ +  {mod, {ch_app,[]}}
        │ │ │ │ + ]}.

        To upgrade ch_app from "1" to "2" (and to downgrade from "2" to "1"), │ │ │ │ you only need to load the new (old) version of the ch3 callback module. Create │ │ │ │ -the application upgrade file ch_app.appup in the ebin directory:

        {"2",
        │ │ │ │ - [{"1", [{load_module, ch3}]}],
        │ │ │ │ - [{"1", [{load_module, ch3}]}]
        │ │ │ │ -}.

        │ │ │ │ +the application upgrade file ch_app.appup in the ebin directory:

        {"2",
        │ │ │ │ + [{"1", [{load_module, ch3}]}],
        │ │ │ │ + [{"1", [{load_module, ch3}]}]
        │ │ │ │ +}.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Release Upgrade File │ │ │ │

        │ │ │ │

        To define how to upgrade/downgrade between the new version and previous versions │ │ │ │ of a release, a release upgrade file, or in short .relup file, is to be │ │ │ │ @@ -310,22 +310,22 @@ │ │ │ │ are to be added and deleted, and which applications that must be upgraded and/or │ │ │ │ downgraded. The instructions for this are fetched from the .appup files and │ │ │ │ transformed into a single list of low-level instructions in the right order.

        If the relup file is relatively simple, it can be created manually. It is only │ │ │ │ to contain low-level instructions.

        For details about the syntax and contents of the release upgrade file, see │ │ │ │ relup in SASL.

        Example, continued from the previous section: You have a new version "2" of │ │ │ │ ch_app and an .appup file. A new version of the .rel file is also needed. │ │ │ │ This time the file is called ch_rel-2.rel and the release version string is │ │ │ │ -changed from "A" to "B":

        {release,
        │ │ │ │ - {"ch_rel", "B"},
        │ │ │ │ - {erts, "14.2.5"},
        │ │ │ │ - [{kernel, "9.2.4"},
        │ │ │ │ -  {stdlib, "5.2.3"},
        │ │ │ │ -  {sasl, "4.2.1"},
        │ │ │ │ -  {ch_app, "2"}]
        │ │ │ │ -}.

        Now the relup file can be generated:

        1> systools:make_relup("ch_rel-2", ["ch_rel-1"], ["ch_rel-1"]).
        │ │ │ │ +changed from "A" to "B":

        {release,
        │ │ │ │ + {"ch_rel", "B"},
        │ │ │ │ + {erts, "14.2.5"},
        │ │ │ │ + [{kernel, "9.2.4"},
        │ │ │ │ +  {stdlib, "5.2.3"},
        │ │ │ │ +  {sasl, "4.2.1"},
        │ │ │ │ +  {ch_app, "2"}]
        │ │ │ │ +}.

        Now the relup file can be generated:

        1> systools:make_relup("ch_rel-2", ["ch_rel-1"], ["ch_rel-1"]).
        │ │ │ │  ok

        This generates a relup file with instructions for how to upgrade from version │ │ │ │ "A" ("ch_rel-1") to version "B" ("ch_rel-2") and how to downgrade from version │ │ │ │ "B" to version "A".

        Both the old and new versions of the .app and .rel files must be in the code │ │ │ │ path, as well as the .appup and (new) .beam files. The code path can be │ │ │ │ extended by using the option path:

        1> systools:make_relup("ch_rel-2", ["ch_rel-1"], ["ch_rel-1"],
        │ │ │ │  [{path,["../ch_rel-1",
        │ │ │ │  "../ch_rel-1/lib/ch_app-1/ebin"]}]).
        │ │ │ │ @@ -338,25 +338,25 @@
        │ │ │ │  

        When you have made a new version of a release, a release package can be created │ │ │ │ with this new version and transferred to the target environment.

        To install the new version of the release in runtime, the release │ │ │ │ handler is used. This is a process belonging to the SASL application, │ │ │ │ which handles unpacking, installation, and removal of release │ │ │ │ packages. The release_handler module communicates with this process.

        Assuming there is an operational target system with installation root directory │ │ │ │ $ROOT, the release package with the new version of the release is to be copied │ │ │ │ to $ROOT/releases.

        First, unpack the release package. The files are then extracted from the │ │ │ │ -package:

        release_handler:unpack_release(ReleaseName) => {ok, Vsn}
        • ReleaseName is the name of the release package except the .tar.gz │ │ │ │ +package:

          release_handler:unpack_release(ReleaseName) => {ok, Vsn}
          • ReleaseName is the name of the release package except the .tar.gz │ │ │ │ extension.
          • Vsn is the version of the unpacked release, as defined in its .rel file.

          A directory $ROOT/lib/releases/Vsn is created, where the .rel file, the boot │ │ │ │ script start.boot, the system configuration file sys.config, and relup are │ │ │ │ placed. For applications with new version numbers, the application directories │ │ │ │ are placed under $ROOT/lib. Unchanged applications are not affected.

          An unpacked release can be installed. The release handler then evaluates the │ │ │ │ -instructions in relup, step by step:

          release_handler:install_release(Vsn) => {ok, FromVsn, []}

          If an error occurs during the installation, the system is rebooted using the old │ │ │ │ +instructions in relup, step by step:

          release_handler:install_release(Vsn) => {ok, FromVsn, []}

          If an error occurs during the installation, the system is rebooted using the old │ │ │ │ version of the release. If installation succeeds, the system is afterwards using │ │ │ │ the new version of the release, but if anything happens and the system is │ │ │ │ rebooted, it starts using the previous version again.

          To be made the default version, the newly installed release must be made │ │ │ │ permanent, which means the previous version becomes old:

          release_handler:make_permanent(Vsn) => ok

          The system keeps information about which versions are old and permanent in the │ │ │ │ -files $ROOT/releases/RELEASES and $ROOT/releases/start_erl.data.

          To downgrade from Vsn to FromVsn, install_release must be called again:

          release_handler:install_release(FromVsn) => {ok, Vsn, []}

          An installed, but not permanent, release can be removed. Information about the │ │ │ │ +files $ROOT/releases/RELEASES and $ROOT/releases/start_erl.data.

          To downgrade from Vsn to FromVsn, install_release must be called again:

          release_handler:install_release(FromVsn) => {ok, Vsn, []}

          An installed, but not permanent, release can be removed. Information about the │ │ │ │ release is then deleted from $ROOT/releases/RELEASES and the release-specific │ │ │ │ code, that is, the new application directories and the $ROOT/releases/Vsn │ │ │ │ directory, are removed.

          release_handler:remove_release(Vsn) => ok

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Example (continued from the previous sections) │ │ │ │ @@ -367,17 +367,17 @@ │ │ │ │ is needed, the file is to contain the empty list:

          [].

          Step 2) Start the system as a simple target system. In reality, it is to be │ │ │ │ started as an embedded system. However, using erl with the correct boot script │ │ │ │ and config file is enough for illustration purposes:

          % cd $ROOT
          │ │ │ │  % bin/erl -boot $ROOT/releases/A/start -config $ROOT/releases/A/sys
          │ │ │ │  ...

          $ROOT is the installation directory of the target system.

          Step 3) In another Erlang shell, generate start scripts and create a release │ │ │ │ package for the new version "B". Remember to include (a possible updated) │ │ │ │ sys.config and the relup file. For more information, see │ │ │ │ -Release Upgrade File.

          1> systools:make_script("ch_rel-2").
          │ │ │ │ +Release Upgrade File.

          1> systools:make_script("ch_rel-2").
          │ │ │ │  ok
          │ │ │ │ -2> systools:make_tar("ch_rel-2").
          │ │ │ │ +2> systools:make_tar("ch_rel-2").
          │ │ │ │  ok

          The new release package now also contains version "2" of ch_app and the │ │ │ │ relup file:

          % tar tf ch_rel-2.tar
          │ │ │ │  lib/kernel-9.2.4/ebin/kernel.app
          │ │ │ │  lib/kernel-9.2.4/ebin/application.beam
          │ │ │ │  ...
          │ │ │ │  lib/stdlib-5.2.3/ebin/stdlib.app
          │ │ │ │  lib/stdlib-5.2.3/ebin/argparse.beam
          │ │ │ │ @@ -390,31 +390,31 @@
          │ │ │ │  lib/ch_app-2/ebin/ch_sup.beam
          │ │ │ │  lib/ch_app-2/ebin/ch3.beam
          │ │ │ │  releases/B/start.boot
          │ │ │ │  releases/B/relup
          │ │ │ │  releases/B/sys.config
          │ │ │ │  releases/B/ch_rel-2.rel
          │ │ │ │  releases/ch_rel-2.rel

          Step 4) Copy the release package ch_rel-2.tar.gz to the $ROOT/releases │ │ │ │ -directory.

          Step 5) In the running target system, unpack the release package:

          1> release_handler:unpack_release("ch_rel-2").
          │ │ │ │ -{ok,"B"}

          The new application version ch_app-2 is installed under $ROOT/lib next to │ │ │ │ +directory.

          Step 5) In the running target system, unpack the release package:

          1> release_handler:unpack_release("ch_rel-2").
          │ │ │ │ +{ok,"B"}

          The new application version ch_app-2 is installed under $ROOT/lib next to │ │ │ │ ch_app-1. The kernel, stdlib, and sasl directories are not affected, as │ │ │ │ they have not changed.

          Under $ROOT/releases, a new directory B is created, containing │ │ │ │ -ch_rel-2.rel, start.boot, sys.config, and relup.

          Step 6) Check if the function ch3:available/0 is available:

          2> ch3:available().
          │ │ │ │ +ch_rel-2.rel, start.boot, sys.config, and relup.

          Step 6) Check if the function ch3:available/0 is available:

          2> ch3:available().
          │ │ │ │  ** exception error: undefined function ch3:available/0

          Step 7) Install the new release. The instructions in $ROOT/releases/B/relup │ │ │ │ are executed one by one, resulting in the new version of ch3 being loaded. The │ │ │ │ -function ch3:available/0 is now available:

          3> release_handler:install_release("B").
          │ │ │ │ -{ok,"A",[]}
          │ │ │ │ -4> ch3:available().
          │ │ │ │ +function ch3:available/0 is now available:

          3> release_handler:install_release("B").
          │ │ │ │ +{ok,"A",[]}
          │ │ │ │ +4> ch3:available().
          │ │ │ │  3
          │ │ │ │ -5> code:which(ch3).
          │ │ │ │ +5> code:which(ch3).
          │ │ │ │  ".../lib/ch_app-2/ebin/ch3.beam"
          │ │ │ │ -6> code:which(ch_sup).
          │ │ │ │ +6> code:which(ch_sup).
          │ │ │ │  ".../lib/ch_app-1/ebin/ch_sup.beam"

          Processes in ch_app for which code have not been updated, for example, the │ │ │ │ supervisor, are still evaluating code from ch_app-1.

          Step 8) If the target system is now rebooted, it uses version "A" again. The │ │ │ │ -"B" version must be made permanent, to be used when the system is rebooted.

          7> release_handler:make_permanent("B").
          │ │ │ │ +"B" version must be made permanent, to be used when the system is rebooted.

          7> release_handler:make_permanent("B").
          │ │ │ │  ok

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Updating Application Specifications │ │ │ │

          │ │ │ │

          When a new version of a release is installed, the application specifications are │ │ │ │ @@ -423,14 +423,14 @@ │ │ │ │ boot script is generated from the same .rel file as is used to build the │ │ │ │ release package itself.

          Specifically, the application configuration parameters are automatically updated │ │ │ │ according to (in increasing priority order):

          • The data in the boot script, fetched from the new application resource file │ │ │ │ App.app
          • The new sys.config
          • Command-line arguments -App Par Val

          This means that parameter values set in the other system configuration files and │ │ │ │ values set using application:set_env/3 are disregarded.

          When an installed release is made permanent, the system process init is set to │ │ │ │ point out the new sys.config.

          After the installation, the application controller compares the old and new │ │ │ │ configuration parameters for all running applications and call the callback │ │ │ │ -function:

          Module:config_change(Changed, New, Removed)
          • Module is the application callback module as defined by the mod key in the │ │ │ │ +function:

            Module:config_change(Changed, New, Removed)
            • Module is the application callback module as defined by the mod key in the │ │ │ │ .app file.
            • Changed and New are lists of {Par,Val} for all changed and added │ │ │ │ configuration parameters, respectively.
            • Removed is a list of all parameters Par that have been removed.

            The function is optional and can be omitted when implementing an application │ │ │ │ callback module.

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/ref_man_records.xhtml │ │ │ │ @@ -28,17 +28,17 @@ │ │ │ │ │ │ │ │ │ │ │ │ Defining Records │ │ │ │

          │ │ │ │

          A record definition consists of the name of the record, followed by the field │ │ │ │ names of the record. Record and field names must be atoms. Each field can be │ │ │ │ given an optional default value. If no default value is supplied, undefined is │ │ │ │ -used.

          -record(Name, {Field1 [= Expr1],
          │ │ │ │ +used.

          -record(Name, {Field1 [= Expr1],
          │ │ │ │                 ...
          │ │ │ │ -               FieldN [= ExprN]}).

          The default value for a field is an arbitrary expression, except that it must │ │ │ │ + FieldN [= ExprN]}).

          The default value for a field is an arbitrary expression, except that it must │ │ │ │ not use any variables.

          A record definition can be placed anywhere among the attributes and function │ │ │ │ declarations of a module, but the definition must come before any usage of the │ │ │ │ record.

          If a record is used in several modules, it is recommended that the record │ │ │ │ definition is placed in an include file.

          Change

          Starting from Erlang/OTP 26, records can be defined in the Erlang shell │ │ │ │ using the syntax described in this section. In earlier releases, it was │ │ │ │ necessary to use the shell built-in function rd/2.

          │ │ │ │ │ │ │ │ @@ -48,32 +48,32 @@ │ │ │ │

          │ │ │ │

          The following expression creates a new Name record where the value of each │ │ │ │ field FieldI is the value of evaluating the corresponding expression ExprI:

          #Name{Field1=Expr1, ..., FieldK=ExprK}

          The fields can be in any order, not necessarily the same order as in the record │ │ │ │ definition, and fields can be omitted. Omitted fields get their respective │ │ │ │ default value instead.

          If several fields are to be assigned the same value, the following construction │ │ │ │ can be used:

          #Name{Field1=Expr1, ..., FieldK=ExprK, _=ExprL}

          Omitted fields then get the value of evaluating ExprL instead of their default │ │ │ │ values. This feature is primarily intended to be used to create patterns for ETS │ │ │ │ -and Mnesia match functions.

          Example:

          -record(person, {name, phone, address}).
          │ │ │ │ +and Mnesia match functions.

          Example:

          -record(person, {name, phone, address}).
          │ │ │ │  
          │ │ │ │ -lookup(Name, Tab) ->
          │ │ │ │ -    ets:match_object(Tab, #person{name=Name, _='_'}).

          │ │ │ │ +lookup(Name, Tab) -> │ │ │ │ + ets:match_object(Tab, #person{name=Name, _='_'}).

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Accessing Record Fields │ │ │ │

          │ │ │ │
          Expr#Name.Field

          Returns the value of the specified field. Expr is to evaluate to a Name │ │ │ │ -record.

          Example:

          -record(person, {name, phone, address}).
          │ │ │ │ +record.

          Example:

          -record(person, {name, phone, address}).
          │ │ │ │  
          │ │ │ │ -get_person_name(Person) ->
          │ │ │ │ +get_person_name(Person) ->
          │ │ │ │      Person#person.name.

          The following expression returns the position of the specified field in the │ │ │ │ -tuple representation of the record:

          #Name.Field

          Example:

          -record(person, {name, phone, address}).
          │ │ │ │ +tuple representation of the record:

          #Name.Field

          Example:

          -record(person, {name, phone, address}).
          │ │ │ │  
          │ │ │ │ -lookup(Name, List) ->
          │ │ │ │ -    lists:keyfind(Name, #person.name, List).

          │ │ │ │ +lookup(Name, List) -> │ │ │ │ + lists:keyfind(Name, #person.name, List).

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Updating Records │ │ │ │

          │ │ │ │
          Expr#Name{Field1=Expr1, ..., FieldK=ExprK}

          Expr is to evaluate to a Name record. A copy of this record is returned, │ │ │ │ with the value of each specified field FieldI changed to the value of │ │ │ │ @@ -83,48 +83,48 @@ │ │ │ │ │ │ │ │ │ │ │ │ Records in Guards │ │ │ │

        │ │ │ │

        Since record expressions are expanded to tuple expressions, creating │ │ │ │ records and accessing record fields are allowed in guards. However, │ │ │ │ all subexpressions (for initializing fields), must be valid guard │ │ │ │ -expressions as well.

        Examples:

        handle(Msg, State) when Msg =:= #msg{to=void, no=3} ->
        │ │ │ │ +expressions as well.

        Examples:

        handle(Msg, State) when Msg =:= #msg{to=void, no=3} ->
        │ │ │ │      ...
        │ │ │ │  
        │ │ │ │ -handle(Msg, State) when State#state.running =:= true ->
        │ │ │ │ -    ...

        There is also a type test BIF is_record(Term, RecordTag).

        Example:

        is_person(P) when is_record(P, person) ->
        │ │ │ │ +handle(Msg, State) when State#state.running =:= true ->
        │ │ │ │ +    ...

        There is also a type test BIF is_record(Term, RecordTag).

        Example:

        is_person(P) when is_record(P, person) ->
        │ │ │ │      true;
        │ │ │ │ -is_person(_P) ->
        │ │ │ │ +is_person(_P) ->
        │ │ │ │      false.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Records in Patterns │ │ │ │

        │ │ │ │

        A pattern that matches a certain record is created in the same way as a record │ │ │ │ is created:

        #Name{Field1=Expr1, ..., FieldK=ExprK}

        In this case, one or more of Expr1 ... ExprK can be unbound variables.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Nested Records │ │ │ │

        │ │ │ │ -

        Assume the following record definitions:

        -record(nrec0, {name = "nested0"}).
        │ │ │ │ --record(nrec1, {name = "nested1", nrec0=#nrec0{}}).
        │ │ │ │ --record(nrec2, {name = "nested2", nrec1=#nrec1{}}).
        │ │ │ │ +

        Assume the following record definitions:

        -record(nrec0, {name = "nested0"}).
        │ │ │ │ +-record(nrec1, {name = "nested1", nrec0=#nrec0{}}).
        │ │ │ │ +-record(nrec2, {name = "nested2", nrec1=#nrec1{}}).
        │ │ │ │  
        │ │ │ │ -N2 = #nrec2{},

        Accessing or updating nested records can be written without parentheses:

        "nested0" = N2#nrec2.nrec1#nrec1.nrec0#nrec0.name,
        │ │ │ │ +N2 = #nrec2{},

        Accessing or updating nested records can be written without parentheses:

        "nested0" = N2#nrec2.nrec1#nrec1.nrec0#nrec0.name,
        │ │ │ │      N0n = N2#nrec2.nrec1#nrec1.nrec0#nrec0{name = "nested0a"},

        which is equivalent to:

        "nested0" = ((N2#nrec2.nrec1)#nrec1.nrec0)#nrec0.name,
        │ │ │ │  N0n = ((N2#nrec2.nrec1)#nrec1.nrec0)#nrec0{name = "nested0a"},

        Change

        Before Erlang/OTP R14, parentheses were necessary when accessing or updating │ │ │ │ nested records.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Internal Representation of Records │ │ │ │

        │ │ │ │

        Record expressions are translated to tuple expressions during compilation. A │ │ │ │ -record defined as:

        -record(Name, {Field1, ..., FieldN}).

        is internally represented by the tuple:

        {Name, Value1, ..., ValueN}

        Here each ValueI is the default value for FieldI.

        To each module using records, a pseudo function is added during compilation to │ │ │ │ -obtain information about records:

        record_info(fields, Record) -> [Field]
        │ │ │ │ -record_info(size, Record) -> Size

        Size is the size of the tuple representation, that is, one more than the │ │ │ │ +record defined as:

        -record(Name, {Field1, ..., FieldN}).

        is internally represented by the tuple:

        {Name, Value1, ..., ValueN}

        Here each ValueI is the default value for FieldI.

        To each module using records, a pseudo function is added during compilation to │ │ │ │ +obtain information about records:

        record_info(fields, Record) -> [Field]
        │ │ │ │ +record_info(size, Record) -> Size

        Size is the size of the tuple representation, that is, one more than the │ │ │ │ number of fields.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/ref_man_processes.xhtml │ │ │ │ @@ -30,18 +30,18 @@ │ │ │ │ (grow and shrink dynamically) with small memory footprint, fast to create and │ │ │ │ terminate, and the scheduling overhead is low.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Process Creation │ │ │ │

        │ │ │ │ -

        A process is created by calling spawn():

        spawn(Module, Name, Args) -> pid()
        │ │ │ │ -  Module = Name = atom()
        │ │ │ │ -  Args = [Arg1,...,ArgN]
        │ │ │ │ -    ArgI = term()

        spawn() creates a new process and returns the pid.

        The new process starts executing in Module:Name(Arg1,...,ArgN) where the │ │ │ │ +

        A process is created by calling spawn():

        spawn(Module, Name, Args) -> pid()
        │ │ │ │ +  Module = Name = atom()
        │ │ │ │ +  Args = [Arg1,...,ArgN]
        │ │ │ │ +    ArgI = term()

        spawn() creates a new process and returns the pid.

        The new process starts executing in Module:Name(Arg1,...,ArgN) where the │ │ │ │ arguments are the elements of the (possible empty) Args argument list.

        There exist a number of different spawn BIFs:

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Registered Processes │ │ │ │

        │ │ │ │

        Besides addressing a process by using its pid, there are also BIFs for │ │ │ ├── OEBPS/ref_man_functions.xhtml │ │ │ │ @@ -25,51 +25,51 @@ │ │ │ │ │ │ │ │ │ │ │ │ Function Declaration Syntax │ │ │ │ │ │ │ │

        A function declaration is a sequence of function clauses separated by │ │ │ │ semicolons, and terminated by a period (.).

        A function clause consists of a clause head and a clause body, separated by │ │ │ │ ->.

        A clause head consists of the function name, an argument list, and an optional │ │ │ │ -guard sequence beginning with the keyword when:

        Name(Pattern11,...,Pattern1N) [when GuardSeq1] ->
        │ │ │ │ +guard sequence beginning with the keyword when:

        Name(Pattern11,...,Pattern1N) [when GuardSeq1] ->
        │ │ │ │      Body1;
        │ │ │ │  ...;
        │ │ │ │ -Name(PatternK1,...,PatternKN) [when GuardSeqK] ->
        │ │ │ │ +Name(PatternK1,...,PatternKN) [when GuardSeqK] ->
        │ │ │ │      BodyK.

        The function name is an atom. Each argument is a pattern.

        The number of arguments N is the arity of the function. A function is │ │ │ │ uniquely defined by the module name, function name, and arity. That is, two │ │ │ │ functions with the same name and in the same module, but with different arities │ │ │ │ are two different functions.

        A function named f in module mod and with arity N is often denoted as │ │ │ │ mod:f/N.

        A clause body consists of a sequence of expressions separated by comma (,):

        Expr1,
        │ │ │ │  ...,
        │ │ │ │  ExprN

        Valid Erlang expressions and guard sequences are described in │ │ │ │ -Expressions.

        Example:

        fact(N) when N > 0 ->  % first clause head
        │ │ │ │ -    N * fact(N-1);     % first clause body
        │ │ │ │ +Expressions.

        Example:

        fact(N) when N > 0 ->  % first clause head
        │ │ │ │ +    N * fact(N-1);     % first clause body
        │ │ │ │  
        │ │ │ │ -fact(0) ->             % second clause head
        │ │ │ │ +fact(0) ->             % second clause head
        │ │ │ │      1.                 % second clause body

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Function Evaluation │ │ │ │

        │ │ │ │

        When a function M:F/N is called, first the code for the function is located. │ │ │ │ If the function cannot be found, an undef runtime error occurs. Notice that │ │ │ │ the function must be exported to be visible outside the module it is defined in.

        If the function is found, the function clauses are scanned sequentially until a │ │ │ │ clause is found that fulfills both of the following two conditions:

        1. The patterns in the clause head can be successfully matched against the given │ │ │ │ arguments.
        2. The guard sequence, if any, is true.

        If such a clause cannot be found, a function_clause runtime error occurs.

        If such a clause is found, the corresponding clause body is evaluated. That is, │ │ │ │ the expressions in the body are evaluated sequentially and the value of the last │ │ │ │ -expression is returned.

        Consider the function fact:

        -module(mod).
        │ │ │ │ --export([fact/1]).
        │ │ │ │ +expression is returned.

        Consider the function fact:

        -module(mod).
        │ │ │ │ +-export([fact/1]).
        │ │ │ │  
        │ │ │ │ -fact(N) when N > 0 ->
        │ │ │ │ -    N * fact(N - 1);
        │ │ │ │ -fact(0) ->
        │ │ │ │ +fact(N) when N > 0 ->
        │ │ │ │ +    N * fact(N - 1);
        │ │ │ │ +fact(0) ->
        │ │ │ │      1.

        Assume that you want to calculate the factorial for 1:

        1> mod:fact(1).

        Evaluation starts at the first clause. The pattern N is matched against │ │ │ │ argument 1. The matching succeeds and the guard (N > 0) is true, thus N is │ │ │ │ -bound to 1, and the corresponding body is evaluated:

        N * fact(N-1) => (N is bound to 1)
        │ │ │ │ -1 * fact(0)

        Now, fact(0) is called, and the function clauses are scanned │ │ │ │ +bound to 1, and the corresponding body is evaluated:

        N * fact(N-1) => (N is bound to 1)
        │ │ │ │ +1 * fact(0)

        Now, fact(0) is called, and the function clauses are scanned │ │ │ │ sequentially again. First, the pattern N is matched against 0. The │ │ │ │ matching succeeds, but the guard (N > 0) is false. Second, the │ │ │ │ pattern 0 is matched against the argument 0. The matching succeeds │ │ │ │ and the body is evaluated:

        1 * fact(0) =>
        │ │ │ │  1 * 1 =>
        │ │ │ │  1

        Evaluation has succeed and mod:fact(1) returns 1.

        If mod:fact/1 is called with a negative number as argument, no clause head │ │ │ │ matches. A function_clause runtime error occurs.

        │ │ │ │ @@ -78,17 +78,17 @@ │ │ │ │ │ │ │ │ Tail recursion │ │ │ │

        │ │ │ │

        If the last expression of a function body is a function call, a │ │ │ │ tail-recursive call is done. This is to ensure that no system │ │ │ │ resources, for example, call stack, are consumed. This means that an │ │ │ │ infinite loop using tail-recursive calls will not exhaust the call │ │ │ │ -stack and can (in principle) run forever.

        Example:

        loop(N) ->
        │ │ │ │ -    io:format("~w~n", [N]),
        │ │ │ │ -    loop(N+1).

        The earlier factorial example is a counter-example. It is not │ │ │ │ +stack and can (in principle) run forever.

        Example:

        loop(N) ->
        │ │ │ │ +    io:format("~w~n", [N]),
        │ │ │ │ +    loop(N+1).

        The earlier factorial example is a counter-example. It is not │ │ │ │ tail-recursive, since a multiplication is done on the result of the recursive │ │ │ │ call to fact(N-1).

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Built-In Functions (BIFs) │ │ │ │

        │ │ │ │ @@ -96,14 +96,14 @@ │ │ │ │ system. BIFs do things that are difficult or impossible to implement │ │ │ │ in Erlang. Most of the BIFs belong to module erlang, but there │ │ │ │ are also BIFs belonging to a few other modules, for example lists │ │ │ │ and ets.

        The most commonly used BIFs belonging to erlang are auto-imported. They do │ │ │ │ not need to be prefixed with the module name. Which BIFs that are auto-imported │ │ │ │ is specified in the erlang module in ERTS. For example, standard-type │ │ │ │ conversion BIFs like atom_to_list and BIFs allowed in guards can be called │ │ │ │ -without specifying the module name.

        Examples:

        1> tuple_size({a,b,c}).
        │ │ │ │ +without specifying the module name.

        Examples:

        1> tuple_size({a,b,c}).
        │ │ │ │  3
        │ │ │ │ -2> atom_to_list('Erlang').
        │ │ │ │ +2> atom_to_list('Erlang').
        │ │ │ │  "Erlang"
        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/records_macros.xhtml │ │ │ │ @@ -29,40 +29,40 @@ │ │ │ │ │ │ │ │

        To illustrate this, the messenger example from the previous section is divided │ │ │ │ into the following five files:

        • mess_config.hrl

          Header file for configuration data

        • mess_interface.hrl

          Interface definitions between the client and the messenger

        • user_interface.erl

          Functions for the user interface

        • mess_client.erl

          Functions for the client side of the messenger

        • mess_server.erl

          Functions for the server side of the messenger

        While doing this, the message passing interface between the shell, the client, │ │ │ │ and the server is cleaned up and is defined using records. Also, macros are │ │ │ │ introduced:

        %%%----FILE mess_config.hrl----
        │ │ │ │  
        │ │ │ │  %%% Configure the location of the server node,
        │ │ │ │ --define(server_node, messenger@super).
        │ │ │ │ +-define(server_node, messenger@super).
        │ │ │ │  
        │ │ │ │  %%%----END FILE----
        %%%----FILE mess_interface.hrl----
        │ │ │ │  
        │ │ │ │  %%% Message interface between client and server and client shell for
        │ │ │ │  %%% messenger program
        │ │ │ │  
        │ │ │ │  %%%Messages from Client to server received in server/1 function.
        │ │ │ │ --record(logon,{client_pid, username}).
        │ │ │ │ --record(message,{client_pid, to_name, message}).
        │ │ │ │ +-record(logon,{client_pid, username}).
        │ │ │ │ +-record(message,{client_pid, to_name, message}).
        │ │ │ │  %%% {'EXIT', ClientPid, Reason}  (client terminated or unreachable.
        │ │ │ │  
        │ │ │ │  %%% Messages from Server to Client, received in await_result/0 function
        │ │ │ │ --record(abort_client,{message}).
        │ │ │ │ +-record(abort_client,{message}).
        │ │ │ │  %%% Messages are: user_exists_at_other_node,
        │ │ │ │  %%%               you_are_not_logged_on
        │ │ │ │ --record(server_reply,{message}).
        │ │ │ │ +-record(server_reply,{message}).
        │ │ │ │  %%% Messages are: logged_on
        │ │ │ │  %%%               receiver_not_found
        │ │ │ │  %%%               sent  (Message has been sent (no guarantee)
        │ │ │ │  %%% Messages from Server to Client received in client/1 function
        │ │ │ │ --record(message_from,{from_name, message}).
        │ │ │ │ +-record(message_from,{from_name, message}).
        │ │ │ │  
        │ │ │ │  %%% Messages from shell to Client received in client/1 function
        │ │ │ │  %%% spawn(mess_client, client, [server_node(), Name])
        │ │ │ │ --record(message_to,{to_name, message}).
        │ │ │ │ +-record(message_to,{to_name, message}).
        │ │ │ │  %%% logoff
        │ │ │ │  
        │ │ │ │  %%%----END FILE----
        %%%----FILE user_interface.erl----
        │ │ │ │  
        │ │ │ │  %%% User interface to the messenger program
        │ │ │ │  %%% login(Name)
        │ │ │ │  %%%     One user at a time can log in from each Erlang node in the
        │ │ │ │ @@ -75,177 +75,177 @@
        │ │ │ │  %%%     Logs off anybody at that node
        │ │ │ │  
        │ │ │ │  %%% message(ToName, Message)
        │ │ │ │  %%%     sends Message to ToName. Error messages if the user of this
        │ │ │ │  %%%     function is not logged on or if ToName is not logged on at
        │ │ │ │  %%%     any node.
        │ │ │ │  
        │ │ │ │ --module(user_interface).
        │ │ │ │ --export([logon/1, logoff/0, message/2]).
        │ │ │ │ --include("mess_interface.hrl").
        │ │ │ │ --include("mess_config.hrl").
        │ │ │ │ +-module(user_interface).
        │ │ │ │ +-export([logon/1, logoff/0, message/2]).
        │ │ │ │ +-include("mess_interface.hrl").
        │ │ │ │ +-include("mess_config.hrl").
        │ │ │ │  
        │ │ │ │ -logon(Name) ->
        │ │ │ │ -    case whereis(mess_client) of
        │ │ │ │ +logon(Name) ->
        │ │ │ │ +    case whereis(mess_client) of
        │ │ │ │          undefined ->
        │ │ │ │ -            register(mess_client,
        │ │ │ │ -                     spawn(mess_client, client, [?server_node, Name]));
        │ │ │ │ +            register(mess_client,
        │ │ │ │ +                     spawn(mess_client, client, [?server_node, Name]));
        │ │ │ │          _ -> already_logged_on
        │ │ │ │      end.
        │ │ │ │  
        │ │ │ │ -logoff() ->
        │ │ │ │ +logoff() ->
        │ │ │ │      mess_client ! logoff.
        │ │ │ │  
        │ │ │ │ -message(ToName, Message) ->
        │ │ │ │ -    case whereis(mess_client) of % Test if the client is running
        │ │ │ │ +message(ToName, Message) ->
        │ │ │ │ +    case whereis(mess_client) of % Test if the client is running
        │ │ │ │          undefined ->
        │ │ │ │              not_logged_on;
        │ │ │ │ -        _ -> mess_client ! #message_to{to_name=ToName, message=Message},
        │ │ │ │ +        _ -> mess_client ! #message_to{to_name=ToName, message=Message},
        │ │ │ │               ok
        │ │ │ │  end.
        │ │ │ │  
        │ │ │ │  %%%----END FILE----
        %%%----FILE mess_client.erl----
        │ │ │ │  
        │ │ │ │  %%% The client process which runs on each user node
        │ │ │ │  
        │ │ │ │ --module(mess_client).
        │ │ │ │ --export([client/2]).
        │ │ │ │ --include("mess_interface.hrl").
        │ │ │ │ -
        │ │ │ │ -client(Server_Node, Name) ->
        │ │ │ │ -    {messenger, Server_Node} ! #logon{client_pid=self(), username=Name},
        │ │ │ │ -    await_result(),
        │ │ │ │ -    client(Server_Node).
        │ │ │ │ +-module(mess_client).
        │ │ │ │ +-export([client/2]).
        │ │ │ │ +-include("mess_interface.hrl").
        │ │ │ │ +
        │ │ │ │ +client(Server_Node, Name) ->
        │ │ │ │ +    {messenger, Server_Node} ! #logon{client_pid=self(), username=Name},
        │ │ │ │ +    await_result(),
        │ │ │ │ +    client(Server_Node).
        │ │ │ │  
        │ │ │ │ -client(Server_Node) ->
        │ │ │ │ +client(Server_Node) ->
        │ │ │ │      receive
        │ │ │ │          logoff ->
        │ │ │ │ -            exit(normal);
        │ │ │ │ -        #message_to{to_name=ToName, message=Message} ->
        │ │ │ │ -            {messenger, Server_Node} !
        │ │ │ │ -                #message{client_pid=self(), to_name=ToName, message=Message},
        │ │ │ │ -            await_result();
        │ │ │ │ -        {message_from, FromName, Message} ->
        │ │ │ │ -            io:format("Message from ~p: ~p~n", [FromName, Message])
        │ │ │ │ +            exit(normal);
        │ │ │ │ +        #message_to{to_name=ToName, message=Message} ->
        │ │ │ │ +            {messenger, Server_Node} !
        │ │ │ │ +                #message{client_pid=self(), to_name=ToName, message=Message},
        │ │ │ │ +            await_result();
        │ │ │ │ +        {message_from, FromName, Message} ->
        │ │ │ │ +            io:format("Message from ~p: ~p~n", [FromName, Message])
        │ │ │ │      end,
        │ │ │ │ -    client(Server_Node).
        │ │ │ │ +    client(Server_Node).
        │ │ │ │  
        │ │ │ │  %%% wait for a response from the server
        │ │ │ │ -await_result() ->
        │ │ │ │ +await_result() ->
        │ │ │ │      receive
        │ │ │ │ -        #abort_client{message=Why} ->
        │ │ │ │ -            io:format("~p~n", [Why]),
        │ │ │ │ -            exit(normal);
        │ │ │ │ -        #server_reply{message=What} ->
        │ │ │ │ -            io:format("~p~n", [What])
        │ │ │ │ +        #abort_client{message=Why} ->
        │ │ │ │ +            io:format("~p~n", [Why]),
        │ │ │ │ +            exit(normal);
        │ │ │ │ +        #server_reply{message=What} ->
        │ │ │ │ +            io:format("~p~n", [What])
        │ │ │ │      after 5000 ->
        │ │ │ │ -            io:format("No response from server~n", []),
        │ │ │ │ -            exit(timeout)
        │ │ │ │ +            io:format("No response from server~n", []),
        │ │ │ │ +            exit(timeout)
        │ │ │ │      end.
        │ │ │ │  
        │ │ │ │  %%%----END FILE---
        %%%----FILE mess_server.erl----
        │ │ │ │  
        │ │ │ │  %%% This is the server process of the messenger service
        │ │ │ │  
        │ │ │ │ --module(mess_server).
        │ │ │ │ --export([start_server/0, server/0]).
        │ │ │ │ --include("mess_interface.hrl").
        │ │ │ │ -
        │ │ │ │ -server() ->
        │ │ │ │ -    process_flag(trap_exit, true),
        │ │ │ │ -    server([]).
        │ │ │ │ +-module(mess_server).
        │ │ │ │ +-export([start_server/0, server/0]).
        │ │ │ │ +-include("mess_interface.hrl").
        │ │ │ │ +
        │ │ │ │ +server() ->
        │ │ │ │ +    process_flag(trap_exit, true),
        │ │ │ │ +    server([]).
        │ │ │ │  
        │ │ │ │  %%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
        │ │ │ │ -server(User_List) ->
        │ │ │ │ -    io:format("User list = ~p~n", [User_List]),
        │ │ │ │ +server(User_List) ->
        │ │ │ │ +    io:format("User list = ~p~n", [User_List]),
        │ │ │ │      receive
        │ │ │ │ -        #logon{client_pid=From, username=Name} ->
        │ │ │ │ -            New_User_List = server_logon(From, Name, User_List),
        │ │ │ │ -            server(New_User_List);
        │ │ │ │ -        {'EXIT', From, _} ->
        │ │ │ │ -            New_User_List = server_logoff(From, User_List),
        │ │ │ │ -            server(New_User_List);
        │ │ │ │ -        #message{client_pid=From, to_name=To, message=Message} ->
        │ │ │ │ -            server_transfer(From, To, Message, User_List),
        │ │ │ │ -            server(User_List)
        │ │ │ │ +        #logon{client_pid=From, username=Name} ->
        │ │ │ │ +            New_User_List = server_logon(From, Name, User_List),
        │ │ │ │ +            server(New_User_List);
        │ │ │ │ +        {'EXIT', From, _} ->
        │ │ │ │ +            New_User_List = server_logoff(From, User_List),
        │ │ │ │ +            server(New_User_List);
        │ │ │ │ +        #message{client_pid=From, to_name=To, message=Message} ->
        │ │ │ │ +            server_transfer(From, To, Message, User_List),
        │ │ │ │ +            server(User_List)
        │ │ │ │      end.
        │ │ │ │  
        │ │ │ │  %%% Start the server
        │ │ │ │ -start_server() ->
        │ │ │ │ -    register(messenger, spawn(?MODULE, server, [])).
        │ │ │ │ +start_server() ->
        │ │ │ │ +    register(messenger, spawn(?MODULE, server, [])).
        │ │ │ │  
        │ │ │ │  %%% Server adds a new user to the user list
        │ │ │ │ -server_logon(From, Name, User_List) ->
        │ │ │ │ +server_logon(From, Name, User_List) ->
        │ │ │ │      %% check if logged on anywhere else
        │ │ │ │ -    case lists:keymember(Name, 2, User_List) of
        │ │ │ │ +    case lists:keymember(Name, 2, User_List) of
        │ │ │ │          true ->
        │ │ │ │ -            From ! #abort_client{message=user_exists_at_other_node},
        │ │ │ │ +            From ! #abort_client{message=user_exists_at_other_node},
        │ │ │ │              User_List;
        │ │ │ │          false ->
        │ │ │ │ -            From ! #server_reply{message=logged_on},
        │ │ │ │ -            link(From),
        │ │ │ │ -            [{From, Name} | User_List]        %add user to the list
        │ │ │ │ +            From ! #server_reply{message=logged_on},
        │ │ │ │ +            link(From),
        │ │ │ │ +            [{From, Name} | User_List]        %add user to the list
        │ │ │ │      end.
        │ │ │ │  
        │ │ │ │  %%% Server deletes a user from the user list
        │ │ │ │ -server_logoff(From, User_List) ->
        │ │ │ │ -    lists:keydelete(From, 1, User_List).
        │ │ │ │ +server_logoff(From, User_List) ->
        │ │ │ │ +    lists:keydelete(From, 1, User_List).
        │ │ │ │  
        │ │ │ │  %%% Server transfers a message between user
        │ │ │ │ -server_transfer(From, To, Message, User_List) ->
        │ │ │ │ +server_transfer(From, To, Message, User_List) ->
        │ │ │ │      %% check that the user is logged on and who he is
        │ │ │ │ -    case lists:keysearch(From, 1, User_List) of
        │ │ │ │ +    case lists:keysearch(From, 1, User_List) of
        │ │ │ │          false ->
        │ │ │ │ -            From ! #abort_client{message=you_are_not_logged_on};
        │ │ │ │ -        {value, {_, Name}} ->
        │ │ │ │ -            server_transfer(From, Name, To, Message, User_List)
        │ │ │ │ +            From ! #abort_client{message=you_are_not_logged_on};
        │ │ │ │ +        {value, {_, Name}} ->
        │ │ │ │ +            server_transfer(From, Name, To, Message, User_List)
        │ │ │ │      end.
        │ │ │ │  %%% If the user exists, send the message
        │ │ │ │ -server_transfer(From, Name, To, Message, User_List) ->
        │ │ │ │ +server_transfer(From, Name, To, Message, User_List) ->
        │ │ │ │      %% Find the receiver and send the message
        │ │ │ │ -    case lists:keysearch(To, 2, User_List) of
        │ │ │ │ +    case lists:keysearch(To, 2, User_List) of
        │ │ │ │          false ->
        │ │ │ │ -            From ! #server_reply{message=receiver_not_found};
        │ │ │ │ -        {value, {ToPid, To}} ->
        │ │ │ │ -            ToPid ! #message_from{from_name=Name, message=Message},
        │ │ │ │ -            From !  #server_reply{message=sent}
        │ │ │ │ +            From ! #server_reply{message=receiver_not_found};
        │ │ │ │ +        {value, {ToPid, To}} ->
        │ │ │ │ +            ToPid ! #message_from{from_name=Name, message=Message},
        │ │ │ │ +            From !  #server_reply{message=sent}
        │ │ │ │      end.
        │ │ │ │  
        │ │ │ │  %%%----END FILE---

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Header Files │ │ │ │

        │ │ │ │

        As shown above, some files have extension .hrl. These are header files that │ │ │ │ -are included in the .erl files by:

        -include("File_Name").

        for example:

        -include("mess_interface.hrl").

        In the case above the file is fetched from the same directory as all the other │ │ │ │ +are included in the .erl files by:

        -include("File_Name").

        for example:

        -include("mess_interface.hrl").

        In the case above the file is fetched from the same directory as all the other │ │ │ │ files in the messenger example. (manual).

        .hrl files can contain any valid Erlang code but are most often used for record │ │ │ │ and macro definitions.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Records │ │ │ │

        │ │ │ │ -

        A record is defined as:

        -record(name_of_record,{field_name1, field_name2, field_name3, ......}).

        For example:

        -record(message_to,{to_name, message}).

        This is equivalent to:

        {message_to, To_Name, Message}

        Creating a record is best illustrated by an example:

        #message_to{message="hello", to_name=fred)

        This creates:

        {message_to, fred, "hello"}

        Notice that you do not have to worry about the order you assign values to the │ │ │ │ +

        A record is defined as:

        -record(name_of_record,{field_name1, field_name2, field_name3, ......}).

        For example:

        -record(message_to,{to_name, message}).

        This is equivalent to:

        {message_to, To_Name, Message}

        Creating a record is best illustrated by an example:

        #message_to{message="hello", to_name=fred)

        This creates:

        {message_to, fred, "hello"}

        Notice that you do not have to worry about the order you assign values to the │ │ │ │ various parts of the records when you create it. The advantage of using records │ │ │ │ is that by placing their definitions in header files you can conveniently define │ │ │ │ interfaces that are easy to change. For example, if you want to add a new field │ │ │ │ to the record, you only have to change the code where the new field is used and │ │ │ │ not at every place the record is referred to. If you leave out a field when │ │ │ │ creating a record, it gets the value of the atom undefined. (manual)

        Pattern matching with records is very similar to creating records. For example, │ │ │ │ -inside a case or receive:

        #message_to{to_name=ToName, message=Message} ->

        This is the same as:

        {message_to, ToName, Message}

        │ │ │ │ +inside a case or receive:

        #message_to{to_name=ToName, message=Message} ->

        This is the same as:

        {message_to, ToName, Message}

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Macros │ │ │ │

        │ │ │ │

        Another thing that has been added to the messenger is a macro. The file │ │ │ │ mess_config.hrl contains the definition:

        %%% Configure the location of the server node,
        │ │ │ │ --define(server_node, messenger@super).

        This file is included in mess_server.erl:

        -include("mess_config.hrl").

        Every occurrence of ?server_node in mess_server.erl is now replaced by │ │ │ │ -messenger@super.

        A macro is also used when spawning the server process:

        spawn(?MODULE, server, [])

        This is a standard macro (that is, defined by the system, not by the user). │ │ │ │ +-define(server_node, messenger@super).

        This file is included in mess_server.erl:

        -include("mess_config.hrl").

        Every occurrence of ?server_node in mess_server.erl is now replaced by │ │ │ │ +messenger@super.

        A macro is also used when spawning the server process:

        spawn(?MODULE, server, [])

        This is a standard macro (that is, defined by the system, not by the user). │ │ │ │ ?MODULE is always replaced by the name of the current module (that is, the │ │ │ │ -module definition near the start of the file). There are more advanced ways │ │ │ │ of using macros with, for example, parameters.

        The three Erlang (.erl) files in the messenger example are individually │ │ │ │ compiled into object code file (.beam). The Erlang system loads and links │ │ │ │ these files into the system when they are referred to during execution of the │ │ │ │ code. In this case, they are simply put in our current working directory (that │ │ │ │ is, the place you have done "cd" to). There are ways of putting the .beam │ │ │ ├── OEBPS/prog_ex_records.xhtml │ │ │ │ @@ -27,105 +27,105 @@ │ │ │ │ Records and Tuples │ │ │ │ │ │ │ │

        The main advantage of using records rather than tuples is that fields in a │ │ │ │ record are accessed by name, whereas fields in a tuple are accessed by position. │ │ │ │ To illustrate these differences, suppose that you want to represent a person │ │ │ │ with the tuple {Name, Address, Phone}.

        To write functions that manipulate this data, remember the following:

        • The Name field is the first element of the tuple.
        • The Address field is the second element.
        • The Phone field is the third element.

        For example, to extract data from a variable P that contains such a tuple, you │ │ │ │ can write the following code and then use pattern matching to extract the │ │ │ │ -relevant fields:

        Name = element(1, P),
        │ │ │ │ -Address = element(2, P),
        │ │ │ │ +relevant fields:

        Name = element(1, P),
        │ │ │ │ +Address = element(2, P),
        │ │ │ │  ...

        Such code is difficult to read and understand, and errors occur if the numbering │ │ │ │ of the elements in the tuple is wrong. If the data representation of the fields │ │ │ │ is changed, by re-ordering, adding, or removing fields, all references to the │ │ │ │ person tuple must be checked and possibly modified.

        Records allow references to the fields by name, instead of by position. In the │ │ │ │ -following example, a record instead of a tuple is used to store the data:

        -record(person, {name, phone, address}).

        This enables references to the fields of the record by name. For example, if P │ │ │ │ +following example, a record instead of a tuple is used to store the data:

        -record(person, {name, phone, address}).

        This enables references to the fields of the record by name. For example, if P │ │ │ │ is a variable whose value is a person record, the following code access the │ │ │ │ name and address fields of the records:

        Name = P#person.name,
        │ │ │ │  Address = P#person.address,
        │ │ │ │ -...

        Internally, records are represented using tagged tuples:

        {person, Name, Phone, Address}

        │ │ │ │ +...

        Internally, records are represented using tagged tuples:

        {person, Name, Phone, Address}

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Defining a Record │ │ │ │

        │ │ │ │

        This following definition of a person is used in several examples in this │ │ │ │ section. Three fields are included, name, phone, and address. The default │ │ │ │ values for name and phone is "" and [], respectively. The default value for │ │ │ │ address is the atom undefined, since no default value is supplied for this │ │ │ │ -field:

        -record(person, {name = "", phone = [], address}).

        The record must be defined in the shell to enable use of the record syntax in │ │ │ │ -the examples:

        > rd(person, {name = "", phone = [], address}).
        │ │ │ │ +field:

        -record(person, {name = "", phone = [], address}).

        The record must be defined in the shell to enable use of the record syntax in │ │ │ │ +the examples:

        > rd(person, {name = "", phone = [], address}).
        │ │ │ │  person

        This is because record definitions are only available at compile time, not at │ │ │ │ runtime. For details on records in the shell, see the shell manual page in │ │ │ │ STDLIB.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Creating a Record │ │ │ │

        │ │ │ │ -

        A new person record is created as follows:

        > #person{phone=[0,8,2,3,4,3,1,2], name="Robert"}.
        │ │ │ │ -#person{name = "Robert",phone = [0,8,2,3,4,3,1,2],address = undefined}

        As the address field was omitted, its default value is used.

        From Erlang 5.1/OTP R8B, a value to all fields in a record can be set with the │ │ │ │ -special field _. _ means "all fields not explicitly specified".

        Example:

        > #person{name = "Jakob", _ = '_'}.
        │ │ │ │ -#person{name = "Jakob",phone = '_',address = '_'}

        It is primarily intended to be used in ets:match/2 and │ │ │ │ +

        A new person record is created as follows:

        > #person{phone=[0,8,2,3,4,3,1,2], name="Robert"}.
        │ │ │ │ +#person{name = "Robert",phone = [0,8,2,3,4,3,1,2],address = undefined}

        As the address field was omitted, its default value is used.

        From Erlang 5.1/OTP R8B, a value to all fields in a record can be set with the │ │ │ │ +special field _. _ means "all fields not explicitly specified".

        Example:

        > #person{name = "Jakob", _ = '_'}.
        │ │ │ │ +#person{name = "Jakob",phone = '_',address = '_'}

        It is primarily intended to be used in ets:match/2 and │ │ │ │ mnesia:match_object/3, to set record fields to the atom '_'. (This is a │ │ │ │ wildcard in ets:match/2.)

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Accessing a Record Field │ │ │ │

        │ │ │ │ -

        The following example shows how to access a record field:

        > P = #person{name = "Joe", phone = [0,8,2,3,4,3,1,2]}.
        │ │ │ │ -#person{name = "Joe",phone = [0,8,2,3,4,3,1,2],address = undefined}
        │ │ │ │ +

        The following example shows how to access a record field:

        > P = #person{name = "Joe", phone = [0,8,2,3,4,3,1,2]}.
        │ │ │ │ +#person{name = "Joe",phone = [0,8,2,3,4,3,1,2],address = undefined}
        │ │ │ │  > P#person.name.
        │ │ │ │  "Joe"

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Updating a Record │ │ │ │

        │ │ │ │ -

        The following example shows how to update a record:

        > P1 = #person{name="Joe", phone=[1,2,3], address="A street"}.
        │ │ │ │ -#person{name = "Joe",phone = [1,2,3],address = "A street"}
        │ │ │ │ -> P2 = P1#person{name="Robert"}.
        │ │ │ │ -#person{name = "Robert",phone = [1,2,3],address = "A street"}

        │ │ │ │ +

        The following example shows how to update a record:

        > P1 = #person{name="Joe", phone=[1,2,3], address="A street"}.
        │ │ │ │ +#person{name = "Joe",phone = [1,2,3],address = "A street"}
        │ │ │ │ +> P2 = P1#person{name="Robert"}.
        │ │ │ │ +#person{name = "Robert",phone = [1,2,3],address = "A street"}

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Type Testing │ │ │ │

        │ │ │ │

        The following example shows that the guard succeeds if P is record of type │ │ │ │ -person:

        foo(P) when is_record(P, person) -> a_person;
        │ │ │ │ -foo(_) -> not_a_person.

        │ │ │ │ +person:

        foo(P) when is_record(P, person) -> a_person;
        │ │ │ │ +foo(_) -> not_a_person.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Pattern Matching │ │ │ │

        │ │ │ │

        Matching can be used in combination with records, as shown in the following │ │ │ │ -example:

        > P3 = #person{name="Joe", phone=[0,0,7], address="A street"}.
        │ │ │ │ -#person{name = "Joe",phone = [0,0,7],address = "A street"}
        │ │ │ │ -> #person{name = Name} = P3, Name.
        │ │ │ │ +example:

        > P3 = #person{name="Joe", phone=[0,0,7], address="A street"}.
        │ │ │ │ +#person{name = "Joe",phone = [0,0,7],address = "A street"}
        │ │ │ │ +> #person{name = Name} = P3, Name.
        │ │ │ │  "Joe"

        The following function takes a list of person records and searches for the │ │ │ │ -phone number of a person with a particular name:

        find_phone([#person{name=Name, phone=Phone} | _], Name) ->
        │ │ │ │ -    {found,  Phone};
        │ │ │ │ -find_phone([_| T], Name) ->
        │ │ │ │ -    find_phone(T, Name);
        │ │ │ │ -find_phone([], Name) ->
        │ │ │ │ +phone number of a person with a particular name:

        find_phone([#person{name=Name, phone=Phone} | _], Name) ->
        │ │ │ │ +    {found,  Phone};
        │ │ │ │ +find_phone([_| T], Name) ->
        │ │ │ │ +    find_phone(T, Name);
        │ │ │ │ +find_phone([], Name) ->
        │ │ │ │      not_found.

        The fields referred to in the pattern can be given in any order.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Nested Records │ │ │ │

        │ │ │ │

        The value of a field in a record can be an instance of a record. Retrieval of │ │ │ │ nested data can be done stepwise, or in a single step, as shown in the following │ │ │ │ -example:

        -record(name, {first = "Robert", last = "Ericsson"}).
        │ │ │ │ --record(person, {name = #name{}, phone}).
        │ │ │ │ +example:

        -record(name, {first = "Robert", last = "Ericsson"}).
        │ │ │ │ +-record(person, {name = #name{}, phone}).
        │ │ │ │  
        │ │ │ │ -demo() ->
        │ │ │ │ -  P = #person{name= #name{first="Robert",last="Virding"}, phone=123},
        │ │ │ │ -  First = (P#person.name)#name.first.

        Here, demo() evaluates to "Robert".

        │ │ │ │ +demo() -> │ │ │ │ + P = #person{name= #name{first="Robert",last="Virding"}, phone=123}, │ │ │ │ + First = (P#person.name)#name.first.

        Here, demo() evaluates to "Robert".

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ A Longer Example │ │ │ │

        │ │ │ │

        Comments are embedded in the following example:

        %% File: person.hrl
        │ │ │ │  
        │ │ │ │ @@ -135,44 +135,44 @@
        │ │ │ │  %%    name:  A string (default is undefined).
        │ │ │ │  %%    age:   An integer (default is undefined).
        │ │ │ │  %%    phone: A list of integers (default is []).
        │ │ │ │  %%    dict:  A dictionary containing various information
        │ │ │ │  %%           about the person.
        │ │ │ │  %%           A {Key, Value} list (default is the empty list).
        │ │ │ │  %%------------------------------------------------------------
        │ │ │ │ --record(person, {name, age, phone = [], dict = []}).
        -module(person).
        │ │ │ │ --include("person.hrl").
        │ │ │ │ --compile(export_all). % For test purposes only.
        │ │ │ │ +-record(person, {name, age, phone = [], dict = []}).
        -module(person).
        │ │ │ │ +-include("person.hrl").
        │ │ │ │ +-compile(export_all). % For test purposes only.
        │ │ │ │  
        │ │ │ │  %% This creates an instance of a person.
        │ │ │ │  %%   Note: The phone number is not supplied so the
        │ │ │ │  %%         default value [] will be used.
        │ │ │ │  
        │ │ │ │ -make_hacker_without_phone(Name, Age) ->
        │ │ │ │ -   #person{name = Name, age = Age,
        │ │ │ │ -           dict = [{computer_knowledge, excellent},
        │ │ │ │ -                   {drinks, coke}]}.
        │ │ │ │ +make_hacker_without_phone(Name, Age) ->
        │ │ │ │ +   #person{name = Name, age = Age,
        │ │ │ │ +           dict = [{computer_knowledge, excellent},
        │ │ │ │ +                   {drinks, coke}]}.
        │ │ │ │  
        │ │ │ │  %% This demonstrates matching in arguments
        │ │ │ │  
        │ │ │ │ -print(#person{name = Name, age = Age,
        │ │ │ │ -              phone = Phone, dict = Dict}) ->
        │ │ │ │ -  io:format("Name: ~s, Age: ~w, Phone: ~w ~n"
        │ │ │ │ -            "Dictionary: ~w.~n", [Name, Age, Phone, Dict]).
        │ │ │ │ +print(#person{name = Name, age = Age,
        │ │ │ │ +              phone = Phone, dict = Dict}) ->
        │ │ │ │ +  io:format("Name: ~s, Age: ~w, Phone: ~w ~n"
        │ │ │ │ +            "Dictionary: ~w.~n", [Name, Age, Phone, Dict]).
        │ │ │ │  
        │ │ │ │  %% Demonstrates type testing, selector, updating.
        │ │ │ │  
        │ │ │ │ -birthday(P) when is_record(P, person) ->
        │ │ │ │ -   P#person{age = P#person.age + 1}.
        │ │ │ │ +birthday(P) when is_record(P, person) ->
        │ │ │ │ +   P#person{age = P#person.age + 1}.
        │ │ │ │  
        │ │ │ │ -register_two_hackers() ->
        │ │ │ │ -   Hacker1 = make_hacker_without_phone("Joe", 29),
        │ │ │ │ -   OldHacker = birthday(Hacker1),
        │ │ │ │ +register_two_hackers() ->
        │ │ │ │ +   Hacker1 = make_hacker_without_phone("Joe", 29),
        │ │ │ │ +   OldHacker = birthday(Hacker1),
        │ │ │ │     % The central_register_server should have
        │ │ │ │     % an interface function for this.
        │ │ │ │ -   central_register_server ! {register_person, Hacker1},
        │ │ │ │ -   central_register_server ! {register_person,
        │ │ │ │ -             OldHacker#person{name = "Robert",
        │ │ │ │ -                              phone = [0,8,3,2,4,5,3,1]}}.
        │ │ │ │ +
        central_register_server ! {register_person, Hacker1}, │ │ │ │ + central_register_server ! {register_person, │ │ │ │ + OldHacker#person{name = "Robert", │ │ │ │ + phone = [0,8,3,2,4,5,3,1]}}.
        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/patterns.xhtml │ │ │ │ @@ -33,16 +33,16 @@ │ │ │ │ succeeds, any unbound variables in the pattern become bound. If the matching │ │ │ │ fails, an exception is raised.

        Examples:

        1> X.
        │ │ │ │  ** 1:1: variable 'X' is unbound **
        │ │ │ │  2> X = 2.
        │ │ │ │  2
        │ │ │ │  3> X + 1.
        │ │ │ │  3
        │ │ │ │ -4> {X, Y} = {1, 2}.
        │ │ │ │ +4> {X, Y} = {1, 2}.
        │ │ │ │  ** exception error: no match of right hand side value {1,2}
        │ │ │ │ -5> {X, Y} = {2, 3}.
        │ │ │ │ -{2,3}
        │ │ │ │ +5> {X, Y} = {2, 3}.
        │ │ │ │ +{2,3}
        │ │ │ │  6> Y.
        │ │ │ │  3
        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/otp-patch-apply.xhtml │ │ │ │ @@ -106,13 +106,13 @@ │ │ │ │ │ │ │ │ Sanity check │ │ │ │ │ │ │ │

        The application dependencies can be checked using the Erlang shell. │ │ │ │ Application dependencies are verified among installed applications by │ │ │ │ otp_patch_apply, but these are not necessarily those actually loaded. │ │ │ │ By calling system_information:sanity_check() one can validate │ │ │ │ -dependencies among applications actually loaded.

        1> system_information:sanity_check().
        │ │ │ │ +dependencies among applications actually loaded.

        1> system_information:sanity_check().
        │ │ │ │  ok

        Please take a look at the reference of sanity_check() for more │ │ │ │ information.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/opaques.xhtml │ │ │ │ @@ -29,24 +29,24 @@ │ │ │ │

        The main use case for opacity in Erlang is to hide the implementation of a data │ │ │ │ type, enabling evolving the API while minimizing the risk of breaking consumers. │ │ │ │ The runtime does not check opacity. Dialyzer provides some opacity-checking, but │ │ │ │ the rest is up to convention.

        Change

        Since Erlang/OTP 28, Dialyzer checks opaques in their defining module in the │ │ │ │ same way as nominals. Outside of the defining module, Dialyzer checks │ │ │ │ opaques for opacity violations.

        This document explains what Erlang opacity is (and the trade-offs involved) via │ │ │ │ the example of the sets:set() data type. This type was │ │ │ │ -defined in the sets module like this:

        -opaque set(Element) :: #set{segs :: segs(Element)}.

        OTP 24 changed the definition to the following in │ │ │ │ -this commit.

        -opaque set(Element) :: #set{segs :: segs(Element)} | #{Element => ?VALUE}.

        And this change was safer and more backwards-compatible than if the type had │ │ │ │ +defined in the sets module like this:

        -opaque set(Element) :: #set{segs :: segs(Element)}.

        OTP 24 changed the definition to the following in │ │ │ │ +this commit.

        -opaque set(Element) :: #set{segs :: segs(Element)} | #{Element => ?VALUE}.

        And this change was safer and more backwards-compatible than if the type had │ │ │ │ been defined with -type instead of -opaque. Here is why: when a module │ │ │ │ defines an -opaque, the contract is that only the defining module should rely │ │ │ │ on the definition of the type: no other modules should rely on the definition.

        This means that code that pattern-matched on set as a record/tuple technically │ │ │ │ broke the contract, and opted in to being potentially broken when the definition │ │ │ │ of set() changed. Before OTP 24, this code printed ok. In OTP 24 it may │ │ │ │ -error:

        case sets:new() of
        │ │ │ │ -    Set when is_tuple(Set) ->
        │ │ │ │ -        io:format("ok")
        │ │ │ │ +error:

        case sets:new() of
        │ │ │ │ +    Set when is_tuple(Set) ->
        │ │ │ │ +        io:format("ok")
        │ │ │ │  end.

        When working with an opaque defined in another module, here are some │ │ │ │ recommendations:

        • Don't examine the underlying type using pattern-matching, guards, or functions │ │ │ │ that reveal the type, such as tuple_size/1 . One exception │ │ │ │ is that =:= and =/= can be used between two opaques with the same name, or │ │ │ │ between an opaque and any(), as those comparisons do not reveal underlying │ │ │ │ types.
        • Use functions provided by the module for working with the type. For │ │ │ │ example, sets module provides sets:new/0, sets:add_element/2, │ │ │ ├── OEBPS/nominals.xhtml │ │ │ │ @@ -28,55 +28,55 @@ │ │ │ │ │ │ │ │

          For user-defined types │ │ │ │ defined with -type, the Erlang compiler will ignore their type names. This │ │ │ │ means the Erlang compiler uses a structural type system. Two types are seen as │ │ │ │ equivalent if their structures are the same. Type comparison is based on the │ │ │ │ structures of the types, not on how the user explicitly defines them. In the │ │ │ │ following example, meter() and foot() are equivalent, and neither differs │ │ │ │ -from the basic type integer().

          -type meter() :: integer().
          │ │ │ │ --type foot() :: integer().

          Nominal typing is an alternative type system. Two nominal types are equivalent │ │ │ │ +from the basic type integer().

          -type meter() :: integer().
          │ │ │ │ +-type foot() :: integer().

          Nominal typing is an alternative type system. Two nominal types are equivalent │ │ │ │ if and only if they are declared with the same type name. The syntax for │ │ │ │ declaring nominal types is -nominal.

          If meter() and foot() are defined as nominal types, they will no longer be │ │ │ │ compatible. When a function expects type meter(), passing in type foot() │ │ │ │ -will result in a warning raised by the type checker.

          -nominal meter() :: integer().
          │ │ │ │ --nominal foot() :: integer().

          The main use case of nominal types is to prevent accidental misuse of types with │ │ │ │ +will result in a warning raised by the type checker.

          -nominal meter() :: integer().
          │ │ │ │ +-nominal foot() :: integer().

          The main use case of nominal types is to prevent accidental misuse of types with │ │ │ │ the same structure. Within OTP, nominal type-checking is done in Dialyzer. The │ │ │ │ Erlang compiler does not perform nominal type-checking.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Nominal Type-Checking Rules │ │ │ │

          │ │ │ │

          In general, if two nominal types have different names, and one is not derived │ │ │ │ from the other, they are not compatible. Dialyzer's nominal type-checking │ │ │ │ -aligns with the examples' expected results in this section.

          If we continue from the example above:

          -spec int_to_meter(integer()) -> meter().
          │ │ │ │ -int_to_meter(X) -> X.
          │ │ │ │ +aligns with the examples' expected results in this section.

          If we continue from the example above:

          -spec int_to_meter(integer()) -> meter().
          │ │ │ │ +int_to_meter(X) -> X.
          │ │ │ │  
          │ │ │ │ --spec foo() -> foot().
          │ │ │ │ -foo() -> int_to_meter(24).

          A type checker that performs nominal type-checking should raise a warning. │ │ │ │ +-spec foo() -> foot(). │ │ │ │ +foo() -> int_to_meter(24).

          A type checker that performs nominal type-checking should raise a warning. │ │ │ │ According to the specification, foo/0 should return a foot() type. However, │ │ │ │ the function int_to_meter/1 returns a meter() type, so foo/0 will also │ │ │ │ return a meter() type. Because meter() and foot() are incompatible │ │ │ │ nominal types, Dialyzer raises the following warning for foo/0:

          Invalid type specification for function foo/0.
          │ │ │ │ -The success typing is foo() -> (meter() :: integer())
          │ │ │ │ -But the spec is foo() -> foot()
          │ │ │ │ +The success typing is foo() -> (meter() :: integer())
          │ │ │ │ +But the spec is foo() -> foot()
          │ │ │ │  The return types do not overlap

          On the other hand, a nominal type is compatible with a non-opaque, non-nominal │ │ │ │ type with the same structure. This compatibility goes both ways, meaning that │ │ │ │ passing a structural type when a nominal type is expected is allowed, and │ │ │ │ -vice versa.

          -spec qaz() -> integer().
          │ │ │ │ -qaz() -> int_to_meter(24).

          A type checker that performs nominal type-checking should not raise a warning │ │ │ │ +vice versa.

          -spec qaz() -> integer().
          │ │ │ │ +qaz() -> int_to_meter(24).

          A type checker that performs nominal type-checking should not raise a warning │ │ │ │ in this case. The specification says that qaz/0 should return an integer() │ │ │ │ type. However, the function int_to_meter/1 returns a meter() type, so │ │ │ │ qaz/0 will also return a meter() type. integer() is not a nominal type. │ │ │ │ The structure of meter() is compatible with integer(). Dialyzer can │ │ │ │ analyze the function above without raising a warning.

          There is one exception where two nominal types with different names can be │ │ │ │ compatible: when one is derived from the other. For nominal types s() and │ │ │ │ -t(), s() can be derived from t() in the two following ways:

          1. If s() is directly derived from t().
          -nominal s() :: t().
          1. If s() is derived from other nominal types, which are derived from t().
          -nominal s() :: nominal_1().
          │ │ │ │ --nominal nominal_1() :: nominal_2().
          │ │ │ │ --nominal nominal_2() :: t().

          In both cases, s() and t() are compatible nominal types even though they │ │ │ │ +t(), s() can be derived from t() in the two following ways:

          1. If s() is directly derived from t().
          -nominal s() :: t().
          1. If s() is derived from other nominal types, which are derived from t().
          -nominal s() :: nominal_1().
          │ │ │ │ +-nominal nominal_1() :: nominal_2().
          │ │ │ │ +-nominal nominal_2() :: t().

          In both cases, s() and t() are compatible nominal types even though they │ │ │ │ have different names. Defining them in different modules does not affect │ │ │ │ compatiblity.

          In summary, nominal type-checking rules are as follows:

          A function that has a -spec that states an argument or a return type to be │ │ │ │ nominal type a/0 (or any other arity), accepts or may return:

          • Nominal type a/0
          • A compatible nominal type b/0
          • A compatible structural type

          A function that has a -spec that states an argument or a return type to be a │ │ │ │ structural type b/0 (or any other arity), accepts or may return:

          • A compatible structural type
          • A compatible nominal type

          When deciding if a type should be nominal, here are some suggestions:

          • If there are other types in the same module with the same structure, and they │ │ │ │ should never be mixed, all of them can benefit from being nominal types.
          • If a type represents a unit like meter, second, byte, and so on, defining it │ │ │ │ as a nominal type is always more useful than -type. You get the nice │ │ │ │ guarantee that you cannot mix them up with other units defined as nominal │ │ │ ├── OEBPS/nif.xhtml │ │ │ │ @@ -38,26 +38,26 @@ │ │ │ │ Erlang Program │ │ │ │ │ │ │ │

            Even if all functions of a module are NIFs, an Erlang module is still needed for │ │ │ │ two reasons:

            • The NIF library must be explicitly loaded by Erlang code in the same module.
            • All NIFs of a module must have an Erlang implementation as well.

            Normally these are minimal stub implementations that throw an exception. But │ │ │ │ they can also be used as fallback implementations for functions that do not have │ │ │ │ native implementations on some architectures.

            NIF libraries are loaded by calling erlang:load_nif/2, with the name of the │ │ │ │ shared library as argument. The second argument can be any term that will be │ │ │ │ -passed on to the library and used for initialization:

            -module(complex6).
            │ │ │ │ --export([foo/1, bar/1]).
            │ │ │ │ --nifs([foo/1, bar/1]).
            │ │ │ │ --on_load(init/0).
            │ │ │ │ -
            │ │ │ │ -init() ->
            │ │ │ │ -    ok = erlang:load_nif("./complex6_nif", 0).
            │ │ │ │ -
            │ │ │ │ -foo(_X) ->
            │ │ │ │ -    erlang:nif_error(nif_library_not_loaded).
            │ │ │ │ -bar(_Y) ->
            │ │ │ │ -    erlang:nif_error(nif_library_not_loaded).

            Here, the directive on_load is used to get function init to be automatically │ │ │ │ +passed on to the library and used for initialization:

            -module(complex6).
            │ │ │ │ +-export([foo/1, bar/1]).
            │ │ │ │ +-nifs([foo/1, bar/1]).
            │ │ │ │ +-on_load(init/0).
            │ │ │ │ +
            │ │ │ │ +init() ->
            │ │ │ │ +    ok = erlang:load_nif("./complex6_nif", 0).
            │ │ │ │ +
            │ │ │ │ +foo(_X) ->
            │ │ │ │ +    erlang:nif_error(nif_library_not_loaded).
            │ │ │ │ +bar(_Y) ->
            │ │ │ │ +    erlang:nif_error(nif_library_not_loaded).

            Here, the directive on_load is used to get function init to be automatically │ │ │ │ called when the module is loaded. If init returns anything other than ok, │ │ │ │ such when the loading of the NIF library fails in this example, the module is │ │ │ │ unloaded and calls to functions within it, fail.

            Loading the NIF library overrides the stub implementations and cause calls to │ │ │ │ foo and bar to be dispatched to the NIF implementations instead.

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -114,22 +114,22 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Running the Example │ │ │ │

            │ │ │ │

            Step 1. Compile the C code:

            unix> gcc -o complex6_nif.so -fpic -shared complex.c complex6_nif.c
            │ │ │ │  windows> cl -LD -MD -Fe complex6_nif.dll complex.c complex6_nif.c

            Step 2: Start Erlang and compile the Erlang code:

            > erl
            │ │ │ │ -Erlang R13B04 (erts-5.7.5) [64-bit] [smp:4:4] [rq:4] [async-threads:0] [kernel-poll:false]
            │ │ │ │ +Erlang R13B04 (erts-5.7.5) [64-bit] [smp:4:4] [rq:4] [async-threads:0] [kernel-poll:false]
            │ │ │ │  
            │ │ │ │ -Eshell V5.7.5  (abort with ^G)
            │ │ │ │ -1> c(complex6).
            │ │ │ │ -{ok,complex6}

            Step 3: Run the example:

            3> complex6:foo(3).
            │ │ │ │ +Eshell V5.7.5  (abort with ^G)
            │ │ │ │ +1> c(complex6).
            │ │ │ │ +{ok,complex6}

            Step 3: Run the example:

            3> complex6:foo(3).
            │ │ │ │  4
            │ │ │ │ -4> complex6:bar(5).
            │ │ │ │ +4> complex6:bar(5).
            │ │ │ │  10
            │ │ │ │ -5> complex6:foo("not an integer").
            │ │ │ │ +5> complex6:foo("not an integer").
            │ │ │ │  ** exception error: bad argument
            │ │ │ │       in function  complex6:foo/1
            │ │ │ │          called as comlpex6:foo("not an integer")
            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/modules.xhtml │ │ │ │ @@ -23,20 +23,20 @@ │ │ │ │

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Module Syntax │ │ │ │

            │ │ │ │

            Erlang code is divided into modules. A module consists of a sequence of │ │ │ │ -attributes and function declarations, each terminated by a period (.).

            Example:

            -module(m).          % module attribute
            │ │ │ │ --export([fact/1]).   % module attribute
            │ │ │ │ +attributes and function declarations, each terminated by a period (.).

            Example:

            -module(m).          % module attribute
            │ │ │ │ +-export([fact/1]).   % module attribute
            │ │ │ │  
            │ │ │ │ -fact(N) when N>0 ->  % beginning of function declaration
            │ │ │ │ -    N * fact(N-1);   %  |
            │ │ │ │ -fact(0) ->           %  |
            │ │ │ │ +fact(N) when N>0 ->  % beginning of function declaration
            │ │ │ │ +    N * fact(N-1);   %  |
            │ │ │ │ +fact(0) ->           %  |
            │ │ │ │      1.               % end of function declaration

            For a description of function declarations, see │ │ │ │ Function Declaration Syntax.

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Module Attributes │ │ │ │

            │ │ │ │ @@ -81,71 +81,71 @@ │ │ │ │ meaning.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Behaviour Module Attribute │ │ │ │

          │ │ │ │

          It is possible to specify that the module is the callback module for a │ │ │ │ -behaviour:

          -behaviour(Behaviour).

          The atom Behaviour gives the name of the behaviour, which can be a │ │ │ │ +behaviour:

          -behaviour(Behaviour).

          The atom Behaviour gives the name of the behaviour, which can be a │ │ │ │ user-defined behaviour or one of the following OTP standard behaviours:

          • gen_server
          • gen_statem
          • gen_event
          • supervisor

          The spelling behavior is also accepted.

          The callback functions of the module can be specified either directly by the │ │ │ │ -exported function behaviour_info/1:

          behaviour_info(callbacks) -> Callbacks.

          or by a -callback attribute for each callback function:

          -callback Name(Arguments) -> Result.

          Here, Arguments is a list of zero or more arguments. The -callback attribute │ │ │ │ +exported function behaviour_info/1:

          behaviour_info(callbacks) -> Callbacks.

          or by a -callback attribute for each callback function:

          -callback Name(Arguments) -> Result.

          Here, Arguments is a list of zero or more arguments. The -callback attribute │ │ │ │ is to be preferred since the extra type information can be used by tools to │ │ │ │ produce documentation or find discrepancies.

          Read more about behaviours and callback modules in │ │ │ │ OTP Design Principles.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Record Definitions │ │ │ │

          │ │ │ │ -

          The same syntax as for module attributes is used for record definitions:

          -record(Record, Fields).

          Record definitions are allowed anywhere in a module, also among the function │ │ │ │ +

          The same syntax as for module attributes is used for record definitions:

          -record(Record, Fields).

          Record definitions are allowed anywhere in a module, also among the function │ │ │ │ declarations. Read more in Records.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Preprocessor │ │ │ │

          │ │ │ │

          The same syntax as for module attributes is used by the preprocessor, which │ │ │ │ -supports file inclusion, macros, and conditional compilation:

          -include("SomeFile.hrl").
          │ │ │ │ --define(Macro, Replacement).

          Read more in Preprocessor.

          │ │ │ │ +supports file inclusion, macros, and conditional compilation:

          -include("SomeFile.hrl").
          │ │ │ │ +-define(Macro, Replacement).

          Read more in Preprocessor.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Setting File and Line │ │ │ │

          │ │ │ │

          The same syntax as for module attributes is used for changing the pre-defined │ │ │ │ -macros ?FILE and ?LINE:

          -file(File, Line).

          This attribute is used by tools, such as Yecc, to inform the compiler that the │ │ │ │ +macros ?FILE and ?LINE:

          -file(File, Line).

          This attribute is used by tools, such as Yecc, to inform the compiler that the │ │ │ │ source program is generated by another tool. It also indicates the │ │ │ │ correspondence of source files to lines of the original user-written file, from │ │ │ │ which the source program is produced.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Types and function specifications │ │ │ │

          │ │ │ │

          A similar syntax as for module attributes is used for specifying types and │ │ │ │ -function specifications:

          -type my_type() :: atom() | integer().
          │ │ │ │ --spec my_function(integer()) -> integer().

          Read more in Types and Function specifications.

          The description is based on │ │ │ │ +function specifications:

          -type my_type() :: atom() | integer().
          │ │ │ │ +-spec my_function(integer()) -> integer().

          Read more in Types and Function specifications.

          The description is based on │ │ │ │ EEP8 - Types and function specifications, │ │ │ │ which is not to be further updated.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Documentation attributes │ │ │ │

          │ │ │ │

          The module attribute -doc(Documentation) is used to provide user documentation │ │ │ │ -for a function/type/callback:

          -doc("Example documentation").
          │ │ │ │ -example() -> ok.

          The attribute should be placed just before the entity it documents.The │ │ │ │ +for a function/type/callback:

          -doc("Example documentation").
          │ │ │ │ +example() -> ok.

          The attribute should be placed just before the entity it documents.The │ │ │ │ parenthesis are optional around Documentation. The allowed values for │ │ │ │ Documentation are:

          • literal string or │ │ │ │ utf-8 encoded binary string - The string │ │ │ │ documenting the entity. Any literal string is allowed, so both │ │ │ │ triple quoted strings and │ │ │ │ sigils that translate to literal strings can be used. │ │ │ │ -The following examples are equivalent:

            -doc("Example \"docs\"").
            │ │ │ │ --doc(<<"Example \"docs\""/utf8>>).
            │ │ │ │ +The following examples are equivalent:

            -doc("Example \"docs\"").
            │ │ │ │ +-doc(<<"Example \"docs\""/utf8>>).
            │ │ │ │  -doc ~S/Example "docs"/.
            │ │ │ │  -doc """
            │ │ │ │     Example "docs"
            │ │ │ │     """
            │ │ │ │  -doc ~B|Example "docs"|.

            For clarity it is recommended to use either normal "strings" or triple │ │ │ │ quoted strings for documentation attributes.

          • {file, file:name/0 } - Read the contents of filename and use │ │ │ │ that as the documentation string.

          • false - Set the current entity as hidden, that is, it should not be │ │ │ │ @@ -158,15 +158,15 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ The feature directive │ │ │ │ │ │ │ │

            While not a module attribute, but rather a directive (since it might affect │ │ │ │ syntax), there is the -feature(..) directive used for enabling and disabling │ │ │ │ -features.

            The syntax is similar to that of an attribute, but has two arguments:

            -feature(FeatureName, enable | disable).

            Note that the feature directive can only appear │ │ │ │ +features.

            The syntax is similar to that of an attribute, but has two arguments:

            -feature(FeatureName, enable | disable).

            Note that the feature directive can only appear │ │ │ │ in a prefix of the module.

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Comments │ │ │ │

            │ │ │ │

            Comments can be placed anywhere in a module except within strings and │ │ │ ├── OEBPS/maps.xhtml │ │ │ │ @@ -53,16 +53,16 @@ │ │ │ │ single function that constructs the map using the map syntax and always use │ │ │ │ it.

          • Always update the map using the := operator (that is, requiring that an │ │ │ │ element with that key already exists). The := operator is slightly more │ │ │ │ efficient, and it helps catching mispellings of keys.

          • Whenever possible, match multiple map elements at once.

          • Whenever possible, update multiple map elements at once.

          • Avoid default values and the maps:get/3 function. If there are default │ │ │ │ values, sharing of keys between different instances of the map will be less │ │ │ │ effective, and it is not possible to match multiple elements having default │ │ │ │ values in one go.

          • To avoid having to deal with a map that may lack some keys, maps:merge/2 can │ │ │ │ -efficiently add multiple default values. For example:

            DefaultMap = #{shoe_size => 42, editor => emacs},
            │ │ │ │ -MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)

          │ │ │ │ +efficiently add multiple default values. For example:

          DefaultMap = #{shoe_size => 42, editor => emacs},
          │ │ │ │ +MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Using Maps as Dictionaries │ │ │ │

        │ │ │ │

        Using a map as a dictionary implies the following usage pattern:

        • Keys are usually variables not known at compile-time.
        • There can be any number of elements in the map.
        • Usually, no more than one element is looked up or updated at once.

        Given that usage pattern, the difference in performance between using the map │ │ │ │ syntax and the maps module is usually small. Therefore, which one to use is │ │ │ │ @@ -72,18 +72,18 @@ │ │ │ │ choice.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Using Maps as Sets │ │ │ │

      │ │ │ │

      Starting in OTP 24, the sets module has an option to represent sets as maps. │ │ │ │ -Examples:

      1> sets:new([{version,2}]).
      │ │ │ │ -#{}
      │ │ │ │ -2> sets:from_list([x,y,z], [{version,2}]).
      │ │ │ │ -#{x => [],y => [],z => []}

      sets backed by maps is generally the most efficient set representation, with a │ │ │ │ +Examples:

      1> sets:new([{version,2}]).
      │ │ │ │ +#{}
      │ │ │ │ +2> sets:from_list([x,y,z], [{version,2}]).
      │ │ │ │ +#{x => [],y => [],z => []}

      sets backed by maps is generally the most efficient set representation, with a │ │ │ │ few possible exceptions:

      • ordsets:intersection/2 can be more efficient than sets:intersection/2. If │ │ │ │ the intersection operation is frequently used and operations that operate on a │ │ │ │ single element in a set (such as is_element/2) are avoided, ordsets can │ │ │ │ be a better choice than sets.
      • If the intersection operation is frequently used and operations that operate │ │ │ │ on a single element in a set (such as is_element/2) must also be efficient, │ │ │ │ gb_sets can potentially be a better choice than sets.
      • If the elements of the set are integers in a fairly compact range, the set can │ │ │ │ be represented as an integer where each bit represents an element in the set. │ │ │ │ @@ -108,18 +108,18 @@ │ │ │ │ for the runtime system).

      • N - The number of elements in the map.

      • Keys - A tuple with keys of the map: {Key1,...,KeyN}. The keys are │ │ │ │ sorted.

      • Value1 - The value corresponding to the first key in the key tuple.

      • ValueN - The value corresponding to the last key in the key tuple.

      As an example, let us look at how the map #{a => foo, z => bar} is │ │ │ │ represented:

      01234
      FLATMAP2{a,z}foobar

      Table: #{a => foo, z => bar}

      Let us update the map: M#{q => baz}. The map now looks like this:

      012345
      FLATMAP3{a,q,z}foobazbar

      Table: #{a => foo, q => baz, z => bar}

      Finally, change the value of one element: M#{z := bird}. The map now looks │ │ │ │ like this:

      012345
      FLATMAP3{a,q,z}foobazbird

      Table: #{a => foo, q => baz, z => bird}

      When the value for an existing key is updated, the key tuple is not updated, │ │ │ │ allowing the key tuple to be shared with other instances of the map that have │ │ │ │ the same keys. In fact, the key tuple can be shared between all maps with the │ │ │ │ same keys with some care. To arrange that, define a function that returns a map. │ │ │ │ -For example:

      new() ->
      │ │ │ │ -    #{a => default, b => default, c => default}.

      Defined like this, the key tuple {a,b,c} will be a global literal. To ensure │ │ │ │ +For example:

      new() ->
      │ │ │ │ +    #{a => default, b => default, c => default}.

      Defined like this, the key tuple {a,b,c} will be a global literal. To ensure │ │ │ │ that the key tuple is shared when creating an instance of the map, always call │ │ │ │ -new() and modify the returned map:

          (SOME_MODULE:new())#{a := 42}.

      Using the map syntax with small maps is particularly efficient. As long as the │ │ │ │ +new() and modify the returned map:

          (SOME_MODULE:new())#{a := 42}.

      Using the map syntax with small maps is particularly efficient. As long as the │ │ │ │ keys are known at compile-time, the map is updated in one go, making the time to │ │ │ │ update a map essentially constant regardless of the number of keys updated. The │ │ │ │ same goes for matching. (When the keys are variables, one or more of the keys │ │ │ │ could be identical, so the operations need to be performed sequentially from │ │ │ │ left to right.)

      The memory size for a small map is the size of all keys and values plus 5 words. │ │ │ │ See Memory for more information about memory sizes.

      │ │ │ │ │ │ │ │ @@ -146,21 +146,21 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Using the Map Syntax │ │ │ │

      │ │ │ │

      Using the map syntax is usually slightly more efficient than using the │ │ │ │ corresponding function in the maps module.

      The gain in efficiency for the map syntax is more noticeable for the following │ │ │ │ -operations that can only be achieved using the map syntax:

      • Matching multiple literal keys
      • Updating multiple literal keys
      • Adding multiple literal keys to a map

      For example:

      DO

      Map = Map1#{x := X, y := Y, z := Z}

      DO NOT

      Map2 = maps:update(x, X, Map1),
      │ │ │ │ -Map3 = maps:update(y, Y, Map2),
      │ │ │ │ -Map = maps:update(z, Z, Map3)

      If the map is a small map, the first example runs roughly three times as fast.

      Note that for variable keys, the elements are updated sequentially from left to │ │ │ │ -right. For example, given the following update with variable keys:

      Map = Map1#{Key1 := X, Key2 := Y, Key3 := Z}

      the compiler rewrites it like this to ensure that the updates are applied from │ │ │ │ -left to right:

      Map2 = Map1#{Key1 := X},
      │ │ │ │ -Map3 = Map2#{Key2 := Y},
      │ │ │ │ -Map = Map3#{Key3 := Z}

      If a key is known to exist in a map, using the := operator is slightly more │ │ │ │ +operations that can only be achieved using the map syntax:

      • Matching multiple literal keys
      • Updating multiple literal keys
      • Adding multiple literal keys to a map

      For example:

      DO

      Map = Map1#{x := X, y := Y, z := Z}

      DO NOT

      Map2 = maps:update(x, X, Map1),
      │ │ │ │ +Map3 = maps:update(y, Y, Map2),
      │ │ │ │ +Map = maps:update(z, Z, Map3)

      If the map is a small map, the first example runs roughly three times as fast.

      Note that for variable keys, the elements are updated sequentially from left to │ │ │ │ +right. For example, given the following update with variable keys:

      Map = Map1#{Key1 := X, Key2 := Y, Key3 := Z}

      the compiler rewrites it like this to ensure that the updates are applied from │ │ │ │ +left to right:

      Map2 = Map1#{Key1 := X},
      │ │ │ │ +Map3 = Map2#{Key2 := Y},
      │ │ │ │ +Map = Map3#{Key3 := Z}

      If a key is known to exist in a map, using the := operator is slightly more │ │ │ │ efficient than using the => operator for a small map.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Using the Functions in the maps Module │ │ │ │

      │ │ │ │

      Here follows some notes about most of the functions in the maps module. For │ │ │ │ @@ -211,23 +211,23 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ maps:get/3 │ │ │ │ │ │ │ │

      As an optimization, the compiler will rewrite a call to maps:get/3 to Erlang │ │ │ │ code similar to the following:

      Result = case Map of
      │ │ │ │ -             #{Key := Value} -> Value;
      │ │ │ │ -             #{} -> Default
      │ │ │ │ +             #{Key := Value} -> Value;
      │ │ │ │ +             #{} -> Default
      │ │ │ │           end

      This is reasonably efficient, but if a small map is used as an alternative to │ │ │ │ using a record it is often better not to rely on default values as it prevents │ │ │ │ sharing of keys, which may in the end use more memory than what you save from │ │ │ │ not storing default values in the map.

      If default values are nevertheless required, instead of calling maps:get/3 │ │ │ │ multiple times, consider putting the default values in a map and merging that │ │ │ │ -map with the other map:

      DefaultMap = #{Key1 => Value2, Key2 => Value2, ..., KeyN => ValueN},
      │ │ │ │ -MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)

      This helps share keys between the default map and the one you applied defaults │ │ │ │ +map with the other map:

      DefaultMap = #{Key1 => Value2, Key2 => Value2, ..., KeyN => ValueN},
      │ │ │ │ +MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)

      This helps share keys between the default map and the one you applied defaults │ │ │ │ to, as long as the default map contains all the keys that will ever be used │ │ │ │ and not just the ones with default values. Whether this is faster than calling │ │ │ │ maps:get/3 multiple times depends on the size of the map and the number of │ │ │ │ default values.

      Change

      Before OTP 26.0 maps:get/3 was implemented by calling the function instead │ │ │ │ of rewriting it as an Erlang expression. It is now slightly faster but can no │ │ │ │ longer be traced.

      │ │ │ │ │ │ │ │ @@ -315,29 +315,29 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ maps:put/3 │ │ │ │

      │ │ │ │

      maps:put/3 is implemented in C.

      If the key is known to already exist in the map, maps:update/3 is slightly │ │ │ │ more efficient than maps:put/3.

      If the compiler can determine that the third argument is always a map, it │ │ │ │ -will rewrite the call to maps:put/3 to use the map syntax for updating the map.

      For example, consider the following function:

      add_to_known_map(Map0, A, B, C) when is_map(Map0) ->
      │ │ │ │ -    Map1 = maps:put(a, A, Map0),
      │ │ │ │ -    Map2 = maps:put(b, B, Map1),
      │ │ │ │ -    maps:put(c, C, Map2).

      The compiler first rewrites each call to maps:put/3 to use the map │ │ │ │ +will rewrite the call to maps:put/3 to use the map syntax for updating the map.

      For example, consider the following function:

      add_to_known_map(Map0, A, B, C) when is_map(Map0) ->
      │ │ │ │ +    Map1 = maps:put(a, A, Map0),
      │ │ │ │ +    Map2 = maps:put(b, B, Map1),
      │ │ │ │ +    maps:put(c, C, Map2).

      The compiler first rewrites each call to maps:put/3 to use the map │ │ │ │ syntax, and subsequently combines the three update operations to a │ │ │ │ -single update operation:

      add_to_known_map(Map0, A, B, C) when is_map(Map0) ->
      │ │ │ │ -    Map0#{a => A, b => B, c => C}.

      If the compiler cannot determine that the third argument is always a │ │ │ │ +single update operation:

      add_to_known_map(Map0, A, B, C) when is_map(Map0) ->
      │ │ │ │ +    Map0#{a => A, b => B, c => C}.

      If the compiler cannot determine that the third argument is always a │ │ │ │ map, it retains the maps:put/3 call. For example, given this │ │ │ │ -function:

      add_to_map(Map0, A, B, C) ->
      │ │ │ │ -    Map1 = maps:put(a, A, Map0),
      │ │ │ │ -    Map2 = maps:put(b, B, Map1),
      │ │ │ │ -    maps:put(c, C, Map2).

      the compiler keeps the first call to maps:put/3, but rewrites │ │ │ │ -and combines the other two calls:

      add_to_map(Map0, A, B, C) ->
      │ │ │ │ -    Map1 = maps:put(a, A, Map0),
      │ │ │ │ -    Map1#{b => B, c => C}.

      Change

      The rewriting of maps:put/3 to the map syntax was introduced in │ │ │ │ +function:

      add_to_map(Map0, A, B, C) ->
      │ │ │ │ +    Map1 = maps:put(a, A, Map0),
      │ │ │ │ +    Map2 = maps:put(b, B, Map1),
      │ │ │ │ +    maps:put(c, C, Map2).

      the compiler keeps the first call to maps:put/3, but rewrites │ │ │ │ +and combines the other two calls:

      add_to_map(Map0, A, B, C) ->
      │ │ │ │ +    Map1 = maps:put(a, A, Map0),
      │ │ │ │ +    Map1#{b => B, c => C}.

      Change

      The rewriting of maps:put/3 to the map syntax was introduced in │ │ │ │ Erlang/OTP 28.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ maps:remove/2 │ │ │ │

      │ │ │ │

      maps:remove/2 is implemented in C.

      │ │ │ ├── OEBPS/macros.xhtml │ │ │ │ @@ -22,56 +22,56 @@ │ │ │ │

      │ │ │ │

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ File Inclusion │ │ │ │

      │ │ │ │ -

      A file can be included as follows:

      -include(File).
      │ │ │ │ --include_lib(File).

      File, a string, is to point out a file. The contents of this file are included │ │ │ │ +

      A file can be included as follows:

      -include(File).
      │ │ │ │ +-include_lib(File).

      File, a string, is to point out a file. The contents of this file are included │ │ │ │ as is, at the position of the directive.

      Include files are typically used for record and macro definitions that are │ │ │ │ shared by several modules. It is recommended to use the file name extension │ │ │ │ .hrl for include files.

      File can start with a path component $VAR, for some string VAR. If that is │ │ │ │ the case, the value of the environment variable VAR as returned by │ │ │ │ os:getenv(VAR) is substituted for $VAR. If os:getenv(VAR) returns false, │ │ │ │ $VAR is left as is.

      If the filename File is absolute (possibly after variable substitution), the │ │ │ │ include file with that name is included. Otherwise, the specified file is │ │ │ │ searched for in the following directories, and in this order:

      1. The current working directory
      2. The directory where the module is being compiled
      3. The directories given by the include option

      For details, see erlc in ERTS and │ │ │ │ -compile in Compiler.

      Examples:

      -include("my_records.hrl").
      │ │ │ │ --include("incdir/my_records.hrl").
      │ │ │ │ --include("/home/user/proj/my_records.hrl").
      │ │ │ │ --include("$PROJ_ROOT/my_records.hrl").

      include_lib is similar to include, but is not to point out an absolute file. │ │ │ │ +compile in Compiler.

      Examples:

      -include("my_records.hrl").
      │ │ │ │ +-include("incdir/my_records.hrl").
      │ │ │ │ +-include("/home/user/proj/my_records.hrl").
      │ │ │ │ +-include("$PROJ_ROOT/my_records.hrl").

      include_lib is similar to include, but is not to point out an absolute file. │ │ │ │ Instead, the first path component (possibly after variable substitution) is │ │ │ │ -assumed to be the name of an application.

      Example:

      -include_lib("kernel/include/file.hrl").

      The code server uses code:lib_dir(kernel) to find the directory of the current │ │ │ │ +assumed to be the name of an application.

      Example:

      -include_lib("kernel/include/file.hrl").

      The code server uses code:lib_dir(kernel) to find the directory of the current │ │ │ │ (latest) version of Kernel, and then the subdirectory include is searched for │ │ │ │ the file file.hrl.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Defining and Using Macros │ │ │ │

      │ │ │ │ -

      A macro is defined as follows:

      -define(Const, Replacement).
      │ │ │ │ --define(Func(Var1,...,VarN), Replacement).

      A macro definition can be placed anywhere among the attributes and function │ │ │ │ +

      A macro is defined as follows:

      -define(Const, Replacement).
      │ │ │ │ +-define(Func(Var1,...,VarN), Replacement).

      A macro definition can be placed anywhere among the attributes and function │ │ │ │ declarations of a module, but the definition must come before any usage of the │ │ │ │ macro.

      If a macro is used in several modules, it is recommended that the macro │ │ │ │ definition is placed in an include file.

      A macro is used as follows:

      ?Const
      │ │ │ │  ?Func(Arg1,...,ArgN)

      Macros are expanded during compilation. A simple macro ?Const is replaced with │ │ │ │ -Replacement.

      Example:

      -define(TIMEOUT, 200).
      │ │ │ │ +Replacement.

      Example:

      -define(TIMEOUT, 200).
      │ │ │ │  ...
      │ │ │ │ -call(Request) ->
      │ │ │ │ -    server:call(refserver, Request, ?TIMEOUT).

      This is expanded to:

      call(Request) ->
      │ │ │ │ -    server:call(refserver, Request, 200).

      A macro ?Func(Arg1,...,ArgN) is replaced with Replacement, where all │ │ │ │ +call(Request) -> │ │ │ │ + server:call(refserver, Request, ?TIMEOUT).

      This is expanded to:

      call(Request) ->
      │ │ │ │ +    server:call(refserver, Request, 200).

      A macro ?Func(Arg1,...,ArgN) is replaced with Replacement, where all │ │ │ │ occurrences of a variable Var from the macro definition are replaced with the │ │ │ │ -corresponding argument Arg.

      Example:

      -define(MACRO1(X, Y), {a, X, b, Y}).
      │ │ │ │ +corresponding argument Arg.

      Example:

      -define(MACRO1(X, Y), {a, X, b, Y}).
      │ │ │ │  ...
      │ │ │ │ -bar(X) ->
      │ │ │ │ -    ?MACRO1(a, b),
      │ │ │ │ -    ?MACRO1(X, 123)

      This is expanded to:

      bar(X) ->
      │ │ │ │ -    {a,a,b,b},
      │ │ │ │ -    {a,X,b,123}.

      It is good programming practice, but not mandatory, to ensure that a macro │ │ │ │ +bar(X) -> │ │ │ │ + ?MACRO1(a, b), │ │ │ │ + ?MACRO1(X, 123)

      This is expanded to:

      bar(X) ->
      │ │ │ │ +    {a,a,b,b},
      │ │ │ │ +    {a,X,b,123}.

      It is good programming practice, but not mandatory, to ensure that a macro │ │ │ │ definition is a valid Erlang syntactic form.

      To view the result of macro expansion, a module can be compiled with the 'P' │ │ │ │ option. compile:file(File, ['P']). This produces a listing of the parsed code │ │ │ │ after preprocessing and parse transforms, in the file File.P.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Predefined Macros │ │ │ │ @@ -90,29 +90,29 @@ │ │ │ │ │ │ │ │ │ │ │ │ Macros Overloading │ │ │ │

      │ │ │ │

      It is possible to overload macros, except for predefined macros. An overloaded │ │ │ │ macro has more than one definition, each with a different number of arguments.

      Change

      Support for overloading of macros was added in Erlang 5.7.5/OTP R13B04.

      A macro ?Func(Arg1,...,ArgN) with a (possibly empty) list of arguments results │ │ │ │ in an error message if there is at least one definition of Func with │ │ │ │ -arguments, but none with N arguments.

      Assuming these definitions:

      -define(F0(), c).
      │ │ │ │ --define(F1(A), A).
      │ │ │ │ --define(C, m:f).

      the following does not work:

      f0() ->
      │ │ │ │ +arguments, but none with N arguments.

      Assuming these definitions:

      -define(F0(), c).
      │ │ │ │ +-define(F1(A), A).
      │ │ │ │ +-define(C, m:f).

      the following does not work:

      f0() ->
      │ │ │ │      ?F0. % No, an empty list of arguments expected.
      │ │ │ │  
      │ │ │ │ -f1(A) ->
      │ │ │ │ -    ?F1(A, A). % No, exactly one argument expected.

      On the other hand,

      f() ->
      │ │ │ │ -    ?C().

      is expanded to

      f() ->
      │ │ │ │ -    m:f().

      │ │ │ │ +f1(A) -> │ │ │ │ + ?F1(A, A). % No, exactly one argument expected.

      On the other hand,

      f() ->
      │ │ │ │ +    ?C().

      is expanded to

      f() ->
      │ │ │ │ +    m:f().

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Removing a macro definition │ │ │ │

      │ │ │ │ -

      A definition of macro can be removed as follows:

      -undef(Macro).

      │ │ │ │ +

      A definition of macro can be removed as follows:

      -undef(Macro).

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Conditional Compilation │ │ │ │

      │ │ │ │

      The following macro directives support conditional compilation:

      • -ifdef(Macro). - Evaluate the following lines only if Macro is │ │ │ │ defined.

      • -ifndef(Macro). - Evaluate the following lines only if Macro is not │ │ │ │ @@ -124,43 +124,43 @@ │ │ │ │ true, and the Condition evaluates to true, the lines following the elif │ │ │ │ are evaluated instead.

      • -endif. - Specifies the end of a series of control flow directives.

      Note

      Macro directives cannot be used inside functions.

      Syntactically, the Condition in if and elif must be a │ │ │ │ guard expression. Other constructs (such as │ │ │ │ a case expression) result in a compilation error.

      As opposed to the standard guard expressions, an expression in an if and │ │ │ │ elif also supports calling the psuedo-function defined(Name), which tests │ │ │ │ whether the Name argument is the name of a previously defined macro. │ │ │ │ defined(Name) evaluates to true if the macro is defined and false │ │ │ │ -otherwise. An attempt to call other functions results in a compilation error.

      Example:

      -module(m).
      │ │ │ │ +otherwise. An attempt to call other functions results in a compilation error.

      Example:

      -module(m).
      │ │ │ │  ...
      │ │ │ │  
      │ │ │ │ --ifdef(debug).
      │ │ │ │ --define(LOG(X), io:format("{~p,~p}: ~p~n", [?MODULE,?LINE,X])).
      │ │ │ │ +-ifdef(debug).
      │ │ │ │ +-define(LOG(X), io:format("{~p,~p}: ~p~n", [?MODULE,?LINE,X])).
      │ │ │ │  -else.
      │ │ │ │ --define(LOG(X), true).
      │ │ │ │ +-define(LOG(X), true).
      │ │ │ │  -endif.
      │ │ │ │  
      │ │ │ │  ...

      When trace output is desired, debug is to be defined when the module m is │ │ │ │ compiled:

      % erlc -Ddebug m.erl
      │ │ │ │  
      │ │ │ │  or
      │ │ │ │  
      │ │ │ │ -1> c(m, {d, debug}).
      │ │ │ │ -{ok,m}

      ?LOG(Arg) is then expanded to a call to io:format/2 and provide the user │ │ │ │ -with some simple trace output.

      Example:

      -module(m)
      │ │ │ │ +1> c(m, {d, debug}).
      │ │ │ │ +{ok,m}

      ?LOG(Arg) is then expanded to a call to io:format/2 and provide the user │ │ │ │ +with some simple trace output.

      Example:

      -module(m)
      │ │ │ │  ...
      │ │ │ │ --if(?OTP_RELEASE >= 25).
      │ │ │ │ +-if(?OTP_RELEASE >= 25).
      │ │ │ │  %% Code that will work in OTP 25 or higher
      │ │ │ │ --elif(?OTP_RELEASE >= 26).
      │ │ │ │ +-elif(?OTP_RELEASE >= 26).
      │ │ │ │  %% Code that will work in OTP 26 or higher
      │ │ │ │  -else.
      │ │ │ │  %% Code that will work in OTP 24 or lower.
      │ │ │ │  -endif.
      │ │ │ │  ...

      This code uses the OTP_RELEASE macro to conditionally select code depending on │ │ │ │ -release.

      Example:

      -module(m)
      │ │ │ │ +release.

      Example:

      -module(m)
      │ │ │ │  ...
      │ │ │ │ --if(?OTP_RELEASE >= 26 andalso defined(debug)).
      │ │ │ │ +-if(?OTP_RELEASE >= 26 andalso defined(debug)).
      │ │ │ │  %% Debugging code that requires OTP 26 or later.
      │ │ │ │  -else.
      │ │ │ │  %% Non-debug code that works in any release.
      │ │ │ │  -endif.
      │ │ │ │  ...

      This code uses the OTP_RELEASE macro and defined(debug) to compile debug │ │ │ │ code only for OTP 26 or later.

      │ │ │ │ │ │ │ │ @@ -175,40 +175,40 @@ │ │ │ │ used. In practice this means it should appear before any -export(..) or record │ │ │ │ definitions.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ -error() and -warning() directives │ │ │ │

      │ │ │ │ -

      The directive -error(Term) causes a compilation error.

      Example:

      -module(t).
      │ │ │ │ --export([version/0]).
      │ │ │ │ +

      The directive -error(Term) causes a compilation error.

      Example:

      -module(t).
      │ │ │ │ +-export([version/0]).
      │ │ │ │  
      │ │ │ │ --ifdef(VERSION).
      │ │ │ │ -version() -> ?VERSION.
      │ │ │ │ +-ifdef(VERSION).
      │ │ │ │ +version() -> ?VERSION.
      │ │ │ │  -else.
      │ │ │ │ --error("Macro VERSION must be defined.").
      │ │ │ │ -version() -> "".
      │ │ │ │ +-error("Macro VERSION must be defined.").
      │ │ │ │ +version() -> "".
      │ │ │ │  -endif.

      The error message will look like this:

      % erlc t.erl
      │ │ │ │ -t.erl:7: -error("Macro VERSION must be defined.").

      The directive -warning(Term) causes a compilation warning.

      Example:

      -module(t).
      │ │ │ │ --export([version/0]).
      │ │ │ │ +t.erl:7: -error("Macro VERSION must be defined.").

      The directive -warning(Term) causes a compilation warning.

      Example:

      -module(t).
      │ │ │ │ +-export([version/0]).
      │ │ │ │  
      │ │ │ │ --ifndef(VERSION).
      │ │ │ │ --warning("Macro VERSION not defined -- using default version.").
      │ │ │ │ --define(VERSION, "0").
      │ │ │ │ +-ifndef(VERSION).
      │ │ │ │ +-warning("Macro VERSION not defined -- using default version.").
      │ │ │ │ +-define(VERSION, "0").
      │ │ │ │  -endif.
      │ │ │ │ -version() -> ?VERSION.

      The warning message will look like this:

      % erlc t.erl
      │ │ │ │ +version() -> ?VERSION.

      The warning message will look like this:

      % erlc t.erl
      │ │ │ │  t.erl:5: Warning: -warning("Macro VERSION not defined -- using default version.").

      Change

      The -error() and -warning() directives were added in Erlang/OTP 19.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Stringifying Macro Arguments │ │ │ │

      │ │ │ │

      The construction ??Arg, where Arg is a macro argument, is expanded to a │ │ │ │ string containing the tokens of the argument. This is similar to the #arg │ │ │ │ -stringifying construction in C.

      Example:

      -define(TESTCALL(Call), io:format("Call ~s: ~w~n", [??Call, Call])).
      │ │ │ │ +stringifying construction in C.

      Example:

      -define(TESTCALL(Call), io:format("Call ~s: ~w~n", [??Call, Call])).
      │ │ │ │  
      │ │ │ │ -?TESTCALL(myfunction(1,2)),
      │ │ │ │ -?TESTCALL(you:function(2,1)).

      results in

      io:format("Call ~s: ~w~n",["myfunction ( 1 , 2 )",myfunction(1,2)]),
      │ │ │ │ -io:format("Call ~s: ~w~n",["you : function ( 2 , 1 )",you:function(2,1)]).

      That is, a trace output, with both the function called and the resulting value.

      │ │ │ │ +
      ?TESTCALL(myfunction(1,2)), │ │ │ │ +?TESTCALL(you:function(2,1)).

      results in

      io:format("Call ~s: ~w~n",["myfunction ( 1 , 2 )",myfunction(1,2)]),
      │ │ │ │ +io:format("Call ~s: ~w~n",["you : function ( 2 , 1 )",you:function(2,1)]).

      That is, a trace output, with both the function called and the resulting value.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/listhandling.xhtml │ │ │ │ @@ -25,101 +25,101 @@ │ │ │ │ │ │ │ │ │ │ │ │ Creating a List │ │ │ │

      │ │ │ │

      Lists can only be built starting from the end and attaching list elements at the │ │ │ │ beginning. If you use the ++ operator as follows, a new list is created that │ │ │ │ is a copy of the elements in List1, followed by List2:

      List1 ++ List2

      Looking at how lists:append/2 or ++ would be implemented in plain Erlang, │ │ │ │ -clearly the first list is copied:

      append([H|T], Tail) ->
      │ │ │ │ -    [H|append(T, Tail)];
      │ │ │ │ -append([], Tail) ->
      │ │ │ │ +clearly the first list is copied:

      append([H|T], Tail) ->
      │ │ │ │ +    [H|append(T, Tail)];
      │ │ │ │ +append([], Tail) ->
      │ │ │ │      Tail.

      When recursing and building a list, it is important to ensure that you attach │ │ │ │ the new elements to the beginning of the list. In this way, you will build one │ │ │ │ -list, not hundreds or thousands of copies of the growing result list.

      Let us first see how it is not to be done:

      DO NOT

      bad_fib(N) ->
      │ │ │ │ -    bad_fib(N, 0, 1, []).
      │ │ │ │ +list, not hundreds or thousands of copies of the growing result list.

      Let us first see how it is not to be done:

      DO NOT

      bad_fib(N) ->
      │ │ │ │ +    bad_fib(N, 0, 1, []).
      │ │ │ │  
      │ │ │ │ -bad_fib(0, _Current, _Next, Fibs) ->
      │ │ │ │ +bad_fib(0, _Current, _Next, Fibs) ->
      │ │ │ │      Fibs;
      │ │ │ │ -bad_fib(N, Current, Next, Fibs) ->
      │ │ │ │ -    bad_fib(N - 1, Next, Current + Next, Fibs ++ [Current]).

      Here more than one list is built. In each iteration step a new list is created │ │ │ │ +bad_fib(N, Current, Next, Fibs) -> │ │ │ │ + bad_fib(N - 1, Next, Current + Next, Fibs ++ [Current]).

      Here more than one list is built. In each iteration step a new list is created │ │ │ │ that is one element longer than the new previous list.

      To avoid copying the result in each iteration, build the list in reverse order │ │ │ │ -and reverse the list when you are done:

      DO

      tail_recursive_fib(N) ->
      │ │ │ │ -    tail_recursive_fib(N, 0, 1, []).
      │ │ │ │ +and reverse the list when you are done:

      DO

      tail_recursive_fib(N) ->
      │ │ │ │ +    tail_recursive_fib(N, 0, 1, []).
      │ │ │ │  
      │ │ │ │ -tail_recursive_fib(0, _Current, _Next, Fibs) ->
      │ │ │ │ -    lists:reverse(Fibs);
      │ │ │ │ -tail_recursive_fib(N, Current, Next, Fibs) ->
      │ │ │ │ -    tail_recursive_fib(N - 1, Next, Current + Next, [Current|Fibs]).

      │ │ │ │ +tail_recursive_fib(0, _Current, _Next, Fibs) -> │ │ │ │ + lists:reverse(Fibs); │ │ │ │ +tail_recursive_fib(N, Current, Next, Fibs) -> │ │ │ │ + tail_recursive_fib(N - 1, Next, Current + Next, [Current|Fibs]).

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ List Comprehensions │ │ │ │

      │ │ │ │ -

      A list comprehension:

      [Expr(E) || E <- List]

      is basically translated to a local function:

      'lc^0'([E|Tail], Expr) ->
      │ │ │ │ -    [Expr(E)|'lc^0'(Tail, Expr)];
      │ │ │ │ -'lc^0'([], _Expr) -> [].

      If the result of the list comprehension will obviously not be used, a list │ │ │ │ -will not be constructed. For example, in this code:

      [io:put_chars(E) || E <- List],
      │ │ │ │ +

      A list comprehension:

      [Expr(E) || E <- List]

      is basically translated to a local function:

      'lc^0'([E|Tail], Expr) ->
      │ │ │ │ +    [Expr(E)|'lc^0'(Tail, Expr)];
      │ │ │ │ +'lc^0'([], _Expr) -> [].

      If the result of the list comprehension will obviously not be used, a list │ │ │ │ +will not be constructed. For example, in this code:

      [io:put_chars(E) || E <- List],
      │ │ │ │  ok.

      or in this code:

      case Var of
      │ │ │ │      ... ->
      │ │ │ │ -        [io:put_chars(E) || E <- List];
      │ │ │ │ +        [io:put_chars(E) || E <- List];
      │ │ │ │      ... ->
      │ │ │ │  end,
      │ │ │ │ -some_function(...),

      the value is not assigned to a variable, not passed to another function, and not │ │ │ │ +some_function(...),

      the value is not assigned to a variable, not passed to another function, and not │ │ │ │ returned. This means that there is no need to construct a list and the compiler │ │ │ │ -will simplify the code for the list comprehension to:

      'lc^0'([E|Tail], Expr) ->
      │ │ │ │ -    Expr(E),
      │ │ │ │ -    'lc^0'(Tail, Expr);
      │ │ │ │ -'lc^0'([], _Expr) -> [].

      The compiler also understands that assigning to _ means that the value will │ │ │ │ -not be used. Therefore, the code in the following example will also be optimized:

      _ = [io:put_chars(E) || E <- List],
      │ │ │ │ +will simplify the code for the list comprehension to:

      'lc^0'([E|Tail], Expr) ->
      │ │ │ │ +    Expr(E),
      │ │ │ │ +    'lc^0'(Tail, Expr);
      │ │ │ │ +'lc^0'([], _Expr) -> [].

      The compiler also understands that assigning to _ means that the value will │ │ │ │ +not be used. Therefore, the code in the following example will also be optimized:

      _ = [io:put_chars(E) || E <- List],
      │ │ │ │  ok.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Deep and Flat Lists │ │ │ │

      │ │ │ │

      lists:flatten/1 builds an entirely new list. It is therefore expensive, and │ │ │ │ even more expensive than the ++ operator (which copies its left argument, │ │ │ │ but not its right argument).

      In the following situations it is unnecessary to call lists:flatten/1:

      • When sending data to a port. Ports understand deep lists so there is no reason │ │ │ │ to flatten the list before sending it to the port.
      • When calling BIFs that accept deep lists, such as │ │ │ │ list_to_binary/1 or │ │ │ │ iolist_to_binary/1.
      • When you know that your list is only one level deep. Use lists:append/1 │ │ │ │ -instead.

      Examples:

      DO

      port_command(Port, DeepList)

      DO NOT

      port_command(Port, lists:flatten(DeepList))

      A common way to send a zero-terminated string to a port is the following:

      DO NOT

      TerminatedStr = String ++ [0],
      │ │ │ │ -port_command(Port, TerminatedStr)

      Instead:

      DO

      TerminatedStr = [String, 0],
      │ │ │ │ -port_command(Port, TerminatedStr)

      DO

      1> lists:append([[1], [2], [3]]).
      │ │ │ │ -[1,2,3]

      DO NOT

      1> lists:flatten([[1], [2], [3]]).
      │ │ │ │ -[1,2,3]

      │ │ │ │ +instead.

    Examples:

    DO

    port_command(Port, DeepList)

    DO NOT

    port_command(Port, lists:flatten(DeepList))

    A common way to send a zero-terminated string to a port is the following:

    DO NOT

    TerminatedStr = String ++ [0],
    │ │ │ │ +port_command(Port, TerminatedStr)

    Instead:

    DO

    TerminatedStr = [String, 0],
    │ │ │ │ +port_command(Port, TerminatedStr)

    DO

    1> lists:append([[1], [2], [3]]).
    │ │ │ │ +[1,2,3]

    DO NOT

    1> lists:flatten([[1], [2], [3]]).
    │ │ │ │ +[1,2,3]

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Recursive List Functions │ │ │ │

    │ │ │ │

    There are two basic ways to write a function that traverses a list and │ │ │ │ produces a new list.

    The first way is writing a body-recursive function:

    %% Add 42 to each integer in the list.
    │ │ │ │ -add_42_body([H|T]) ->
    │ │ │ │ -    [H + 42 | add_42_body(T)];
    │ │ │ │ -add_42_body([]) ->
    │ │ │ │ -    [].

    The second way is writing a tail-recursive function:

    %% Add 42 to each integer in the list.
    │ │ │ │ -add_42_tail(List) ->
    │ │ │ │ -    add_42_tail(List, []).
    │ │ │ │ +add_42_body([H|T]) ->
    │ │ │ │ +    [H + 42 | add_42_body(T)];
    │ │ │ │ +add_42_body([]) ->
    │ │ │ │ +    [].

    The second way is writing a tail-recursive function:

    %% Add 42 to each integer in the list.
    │ │ │ │ +add_42_tail(List) ->
    │ │ │ │ +    add_42_tail(List, []).
    │ │ │ │  
    │ │ │ │ -add_42_tail([H|T], Acc) ->
    │ │ │ │ -    add_42_tail(T, [H + 42 | Acc]);
    │ │ │ │ -add_42_tail([], Acc) ->
    │ │ │ │ -    lists:reverse(Acc).

    In early version of Erlang the tail-recursive function would typically │ │ │ │ +add_42_tail([H|T], Acc) -> │ │ │ │ + add_42_tail(T, [H + 42 | Acc]); │ │ │ │ +add_42_tail([], Acc) -> │ │ │ │ + lists:reverse(Acc).

In early version of Erlang the tail-recursive function would typically │ │ │ │ be more efficient. In modern versions of Erlang, there is usually not │ │ │ │ much difference in performance between a body-recursive list function and │ │ │ │ tail-recursive function that reverses the list at the end. Therefore, │ │ │ │ concentrate on writing beautiful code and forget about the performance │ │ │ │ of your list functions. In the time-critical parts of your code, │ │ │ │ measure before rewriting your code.

For a thorough discussion about tail and body recursion, see │ │ │ │ Erlang's Tail Recursion is Not a Silver Bullet.

Note

This section is about list functions that construct lists. A tail-recursive │ │ │ │ function that does not construct a list runs in constant space, while the │ │ │ │ corresponding body-recursive function uses stack space proportional to the │ │ │ │ length of the list.

For example, a function that sums a list of integers, is not to be written as │ │ │ │ -follows:

DO NOT

recursive_sum([H|T]) -> H+recursive_sum(T);
│ │ │ │ -recursive_sum([])    -> 0.

Instead:

DO

sum(L) -> sum(L, 0).
│ │ │ │ +follows:

DO NOT

recursive_sum([H|T]) -> H+recursive_sum(T);
│ │ │ │ +recursive_sum([])    -> 0.

Instead:

DO

sum(L) -> sum(L, 0).
│ │ │ │  
│ │ │ │ -sum([H|T], Sum) -> sum(T, Sum + H);
│ │ │ │ -sum([], Sum)    -> Sum.
│ │ │ │ +
sum([H|T], Sum) -> sum(T, Sum + H); │ │ │ │ +sum([], Sum) -> Sum.
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/list_comprehensions.xhtml │ │ │ │ @@ -22,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 integers {A,B,C} such that │ │ │ │ A**2 + B**2 = C**2 and where the sum of the sides is equal to, or less than, │ │ │ │ -N:

pyth(N) ->
│ │ │ │ -    [ {A,B,C} ||
│ │ │ │ -        A <:- lists:seq(1,N),
│ │ │ │ -        B <:- lists:seq(1,N),
│ │ │ │ -        C <:- lists:seq(1,N),
│ │ │ │ +N:

pyth(N) ->
│ │ │ │ +    [ {A,B,C} ||
│ │ │ │ +        A <:- lists:seq(1,N),
│ │ │ │ +        B <:- lists:seq(1,N),
│ │ │ │ +        C <:- lists:seq(1,N),
│ │ │ │          A+B+C =< N,
│ │ │ │          A*A+B*B == C*C
│ │ │ │ -    ].
> pyth(3).
│ │ │ │ -[].
│ │ │ │ -> pyth(11).
│ │ │ │ -[].
│ │ │ │ -> pyth(12).
│ │ │ │ -[{3,4,5},{4,3,5}]
│ │ │ │ -> pyth(50).
│ │ │ │ -[{3,4,5},
│ │ │ │ - {4,3,5},
│ │ │ │ - {5,12,13},
│ │ │ │ - {6,8,10},
│ │ │ │ - {8,6,10},
│ │ │ │ - {8,15,17},
│ │ │ │ - {9,12,15},
│ │ │ │ - {12,5,13},
│ │ │ │ - {12,9,15},
│ │ │ │ - {12,16,20},
│ │ │ │ - {15,8,17},
│ │ │ │ - {16,12,20}]

The following code reduces the search space and is more efficient:

pyth1(N) ->
│ │ │ │ -   [{A,B,C} ||
│ │ │ │ -       A <:- lists:seq(1,N-2),
│ │ │ │ -       B <:- lists:seq(A+1,N-1),
│ │ │ │ -       C <:- lists:seq(B+1,N),
│ │ │ │ +    ].
> pyth(3).
│ │ │ │ +[].
│ │ │ │ +> pyth(11).
│ │ │ │ +[].
│ │ │ │ +> pyth(12).
│ │ │ │ +[{3,4,5},{4,3,5}]
│ │ │ │ +> pyth(50).
│ │ │ │ +[{3,4,5},
│ │ │ │ + {4,3,5},
│ │ │ │ + {5,12,13},
│ │ │ │ + {6,8,10},
│ │ │ │ + {8,6,10},
│ │ │ │ + {8,15,17},
│ │ │ │ + {9,12,15},
│ │ │ │ + {12,5,13},
│ │ │ │ + {12,9,15},
│ │ │ │ + {12,16,20},
│ │ │ │ + {15,8,17},
│ │ │ │ + {16,12,20}]

The following code reduces the search space and is more efficient:

pyth1(N) ->
│ │ │ │ +   [{A,B,C} ||
│ │ │ │ +       A <:- lists:seq(1,N-2),
│ │ │ │ +       B <:- lists:seq(A+1,N-1),
│ │ │ │ +       C <:- lists:seq(B+1,N),
│ │ │ │         A+B+C =< N,
│ │ │ │ -       A*A+B*B == C*C ].

│ │ │ │ + A*A+B*B == C*C ].

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Simplifications With List Comprehensions │ │ │ │

│ │ │ │

As an example, list comprehensions can be used to simplify some of the functions │ │ │ │ -in lists.erl:

append(L)   ->  [X || L1 <:- L, X <:- L1].
│ │ │ │ -map(Fun, L) -> [Fun(X) || X <:- L].
│ │ │ │ -filter(Pred, L) -> [X || X <:- L, Pred(X)].
│ │ │ │ -zip(L1, L2) -> [{X,Y} || X <:- L1 && Y <:- L2].

│ │ │ │ +in lists.erl:

append(L)   ->  [X || L1 <:- L, X <:- L1].
│ │ │ │ +map(Fun, L) -> [Fun(X) || X <:- L].
│ │ │ │ +filter(Pred, L) -> [X || X <:- L, Pred(X)].
│ │ │ │ +zip(L1, L2) -> [{X,Y} || X <:- L1 && Y <:- L2].

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Variable Bindings in List Comprehensions │ │ │ │

│ │ │ │

The scope rules for variables that occur in list comprehensions are as follows:

  • All variables that occur in a generator pattern are assumed to be "fresh" │ │ │ │ variables.
  • Any variables that are defined before the list comprehension, and that are │ │ │ │ used in filters, have the values they had before the list comprehension.
  • Variables cannot be exported from a list comprehension.
  • Within a zip generator, binding of all variables happen at the same time.

As an example of these rules, suppose you want to write the function select, │ │ │ │ which selects certain elements from a list of tuples. Suppose you write │ │ │ │ select(X, L) -> [Y || {X, Y} <- L]. with the intention of extracting all │ │ │ │ tuples from L, where the first item is X.

Compiling this gives the following diagnostic:

./FileName.erl:Line: Warning: variable 'X' shadowed in generate

This diagnostic warns that the variable X in the pattern is not the same as │ │ │ │ -the variable X that occurs in the function head.

Evaluating select gives the following result:

> select(b,[{a,1},{b,2},{c,3},{b,7}]).
│ │ │ │ -[1,2,3,7]

This is not the wanted result. To achieve the desired effect, select must be │ │ │ │ -written as follows:

select(X, L) ->  [Y || {X1, Y} <- L, X == X1].

The generator now contains unbound variables and the test has been moved into │ │ │ │ -the filter.

This now works as expected:

> select(b,[{a,1},{b,2},{c,3},{b,7}]).
│ │ │ │ -[2,7]

Also note that a variable in a generator pattern will shadow a variable with the │ │ │ │ -same name bound in a previous generator pattern. For example:

> [{X,Y} || X <- [1,2,3], X=Y <- [a,b,c]].
│ │ │ │ -[{a,a},{b,b},{c,c},{a,a},{b,b},{c,c},{a,a},{b,b},{c,c}]

A consequence of the rules for importing variables into a list comprehensions is │ │ │ │ +the variable X that occurs in the function head.

Evaluating select gives the following result:

> select(b,[{a,1},{b,2},{c,3},{b,7}]).
│ │ │ │ +[1,2,3,7]

This is not the wanted result. To achieve the desired effect, select must be │ │ │ │ +written as follows:

select(X, L) ->  [Y || {X1, Y} <- L, X == X1].

The generator now contains unbound variables and the test has been moved into │ │ │ │ +the filter.

This now works as expected:

> select(b,[{a,1},{b,2},{c,3},{b,7}]).
│ │ │ │ +[2,7]

Also note that a variable in a generator pattern will shadow a variable with the │ │ │ │ +same name bound in a previous generator pattern. For example:

> [{X,Y} || X <- [1,2,3], X=Y <- [a,b,c]].
│ │ │ │ +[{a,a},{b,b},{c,c},{a,a},{b,b},{c,c},{a,a},{b,b},{c,c}]

A consequence of the rules for importing variables into a list comprehensions is │ │ │ │ that certain pattern matching operations must be moved into the filters and │ │ │ │ -cannot be written directly in the generators.

To illustrate this, do not write as follows:

f(...) ->
│ │ │ │ +cannot be written directly in the generators.

To illustrate this, do not write as follows:

f(...) ->
│ │ │ │      Y = ...
│ │ │ │ -    [ Expression || PatternInvolving Y  <- Expr, ...]
│ │ │ │ -    ...

Instead, write as follows:

f(...) ->
│ │ │ │ +    [ Expression || PatternInvolving Y  <- Expr, ...]
│ │ │ │ +    ...

Instead, write as follows:

f(...) ->
│ │ │ │      Y = ...
│ │ │ │ -    [ Expression || PatternInvolving Y1  <- Expr, Y == Y1, ...]
│ │ │ │ +    [ Expression || PatternInvolving Y1  <- Expr, Y == Y1, ...]
│ │ │ │      ...

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Strict and Relaxed Generators │ │ │ │

│ │ │ │

Strict and relaxed generators have different behaviors when the right-hand │ │ │ │ side expression does not match the left-hand side pattern. A relaxed generator │ │ │ │ ignores that term and continues on. A strict generator fails with an exception.

Their difference can be shown in the following example. The generator │ │ │ │ expects a two-tuple pattern. If a relaxed generator is used, b will be │ │ │ │ silently skipped. If a strict generator is used, an exception will be raised │ │ │ │ -when the pattern matching fails with b.

{_,_} <-  [{ok, a}, b]
│ │ │ │ -{_,_} <:- [{ok, a}, b]

Semantically, strict or relaxed generators convey different intentions from │ │ │ │ +when the pattern matching fails with b.

{_,_} <-  [{ok, a}, b]
│ │ │ │ +{_,_} <:- [{ok, a}, b]

Semantically, strict or relaxed generators convey different intentions from │ │ │ │ the programmer. Strict generators are used when unexpected elements in the │ │ │ │ input data should not be tolerated. Any element not conforming to specific │ │ │ │ patterns should immediately crash the comprehension, because the program may │ │ │ │ not be prepared to handle it.

For example, the following comprehension is rewritten from one in the Erlang │ │ │ │ linter. It extracts arities from all defined functions. All elements in the │ │ │ │ list DefinedFuns are two-tuples, containing name and arity for functions. │ │ │ │ If any of them differs from this pattern, it means that something has added │ │ │ │ an invalid item into the list of defined functions. It is better for the linter │ │ │ │ to crash in the comprehension than skipping the invalid item and continue │ │ │ │ running. Using a strict generator here is correct, because the linter should │ │ │ │ -not hide the presence of an internal inconsistency.

[Arity || {_FunName, Arity} <:- DefinedFuns]

In contrast, relaxed generators are used when unexpected elements in the input │ │ │ │ +not hide the presence of an internal inconsistency.

[Arity || {_FunName, Arity} <:- DefinedFuns]

In contrast, relaxed generators are used when unexpected elements in the input │ │ │ │ data should be filtered out. The programmer is aware that some elements │ │ │ │ may not conform to specific patterns. Those elements can be safely excluded │ │ │ │ from the comprehension result.

For example, the following comprehension is from a compiler module that │ │ │ │ transforms normal Erlang code to Core Erlang. It finds all defined functions │ │ │ │ from an abstract form, and output them in two-tuples, each containing name and │ │ │ │ arity of a function. Not all forms are function declarations. All the forms │ │ │ │ that are not function declarations should be ignored by this comprehensions. │ │ │ │ Using a relaxed generator here is correct, because the programmer intends to │ │ │ │ -exclude all elements with other patterns.

[{Name,Arity} || {function,_,Name,Arity,_} <- Forms]

Strict and relaxed generators don't always have distinct use cases. When the │ │ │ │ +exclude all elements with other patterns.

[{Name,Arity} || {function,_,Name,Arity,_} <- Forms]

Strict and relaxed generators don't always have distinct use cases. When the │ │ │ │ left-hand side pattern of a generator is a fresh variable, pattern matching │ │ │ │ cannot fail. Using either strict or relaxed generators leads to the same │ │ │ │ behavior. While the preference and use cases might be individual, it is │ │ │ │ recommended to use strict generators when either can be used. Using strict │ │ │ │ generators by default aligns with Erlang's "Let it crash" philosophy.

│ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/install-win32.xhtml │ │ │ │ @@ -200,15 +200,15 @@ │ │ │ │
$ cd erts/emulator │ │ │ │ $ make debug │ │ │ │ $ cd ../etc │ │ │ │ $ make debug │ │ │ │

and sometimes

$ cd $ERL_TOP
│ │ │ │  $ make local_setup
│ │ │ │  

So now when you run $ERL_TOP/erl.exe, you should have a debug compiled │ │ │ │ -emulator, which you will see if you do a:

1> erlang:system_info(system_version).

in the erlang shell. If the returned string contains [debug], you │ │ │ │ +emulator, which you will see if you do a:

1> erlang:system_info(system_version).

in the erlang shell. If the returned string contains [debug], you │ │ │ │ got a debug compiled emulator.

To hack the erlang libraries, you simply do a make opt in the │ │ │ │ specific "applications" directory, like:

$ cd $ERL_TOP/lib/stdlib
│ │ │ │  $ make opt
│ │ │ │  

or even in the source directory...

$ cd $ERL_TOP/lib/stdlib/src
│ │ │ │  $ make opt
│ │ │ │  

Note that you're expected to have a fresh Erlang in your path when │ │ │ │ doing this, preferably the plain 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 shutdown, the gen_server then calls │ │ │ │ -the callback function terminate(shutdown, State):

    init(Args) ->
    │ │ │ │ +the callback function terminate(shutdown, State):

    init(Args) ->
    │ │ │ │      ...,
    │ │ │ │ -    process_flag(trap_exit, true),
    │ │ │ │ +    process_flag(trap_exit, true),
    │ │ │ │      ...,
    │ │ │ │ -    {ok, State}.
    │ │ │ │ +    {ok, State}.
    │ │ │ │  
    │ │ │ │  ...
    │ │ │ │  
    │ │ │ │ -terminate(shutdown, State) ->
    │ │ │ │ +terminate(shutdown, State) ->
    │ │ │ │      %% Code for cleaning up here
    │ │ │ │      ...
    │ │ │ │      ok.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Standalone Gen_Servers │ │ │ │

    │ │ │ │

    If the gen_server is not part of a supervision tree, a stop function │ │ │ │ can be useful, for example:

    ...
    │ │ │ │ -export([stop/0]).
    │ │ │ │ +export([stop/0]).
    │ │ │ │  ...
    │ │ │ │  
    │ │ │ │ -stop() ->
    │ │ │ │ -    gen_server:cast(ch3, stop).
    │ │ │ │ +stop() ->
    │ │ │ │ +    gen_server:cast(ch3, stop).
    │ │ │ │  ...
    │ │ │ │  
    │ │ │ │ -handle_cast(stop, State) ->
    │ │ │ │ -    {stop, normal, State};
    │ │ │ │ -handle_cast({free, Ch}, State) ->
    │ │ │ │ +handle_cast(stop, State) ->
    │ │ │ │ +    {stop, normal, State};
    │ │ │ │ +handle_cast({free, Ch}, State) ->
    │ │ │ │      ...
    │ │ │ │  
    │ │ │ │  ...
    │ │ │ │  
    │ │ │ │ -terminate(normal, State) ->
    │ │ │ │ +terminate(normal, State) ->
    │ │ │ │      ok.

    The callback function handling the stop request returns a tuple │ │ │ │ {stop,normal,State1}, where normal specifies that it is │ │ │ │ a normal termination and State1 is a new value for the state │ │ │ │ of the gen_server. This causes the gen_server to call │ │ │ │ terminate(normal, State1) and then it terminates gracefully.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Handling Other Messages │ │ │ │

    │ │ │ │

    If the gen_server is to be able to receive other messages than requests, │ │ │ │ the callback function handle_info(Info, State) must be implemented │ │ │ │ to handle them. Examples of other messages are exit messages, │ │ │ │ if the gen_server is linked to other processes than the supervisor │ │ │ │ -and it is trapping exit signals.

    handle_info({'EXIT', Pid, Reason}, State) ->
    │ │ │ │ +and it is trapping exit signals.

    handle_info({'EXIT', Pid, Reason}, State) ->
    │ │ │ │      %% Code to handle exits here.
    │ │ │ │      ...
    │ │ │ │ -    {noreply, State1}.

    The final function to implement is code_change/3:

    code_change(OldVsn, State, Extra) ->
    │ │ │ │ +    {noreply, State1}.

    The final function to implement is code_change/3:

    code_change(OldVsn, State, Extra) ->
    │ │ │ │      %% Code to convert state (and more) during code change.
    │ │ │ │      ...
    │ │ │ │ -    {ok, NewState}.
    │ │ │ │ +
    {ok, NewState}.
    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/funs.xhtml │ │ │ │ @@ -22,399 +22,399 @@ │ │ │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ map │ │ │ │

    │ │ │ │ -

    The following function, double, doubles every element in a list:

    double([H|T]) -> [2*H|double(T)];
    │ │ │ │ -double([])    -> [].

    Hence, the argument entered as input is doubled as follows:

    > double([1,2,3,4]).
    │ │ │ │ -[2,4,6,8]

    The following function, add_one, adds one to every element in a list:

    add_one([H|T]) -> [H+1|add_one(T)];
    │ │ │ │ -add_one([])    -> [].

    The functions double and add_one have a similar structure. This can be used │ │ │ │ -by writing a function map that expresses this similarity:

    map(F, [H|T]) -> [F(H)|map(F, T)];
    │ │ │ │ -map(F, [])    -> [].

    The functions double and add_one can now be expressed in terms of map as │ │ │ │ -follows:

    double(L)  -> map(fun(X) -> 2*X end, L).
    │ │ │ │ -add_one(L) -> map(fun(X) -> 1 + X end, L).

    map(F, List) is a function that takes a function F and a list L as │ │ │ │ +

    The following function, double, doubles every element in a list:

    double([H|T]) -> [2*H|double(T)];
    │ │ │ │ +double([])    -> [].

    Hence, the argument entered as input is doubled as follows:

    > double([1,2,3,4]).
    │ │ │ │ +[2,4,6,8]

    The following function, add_one, adds one to every element in a list:

    add_one([H|T]) -> [H+1|add_one(T)];
    │ │ │ │ +add_one([])    -> [].

    The functions double and add_one have a similar structure. This can be used │ │ │ │ +by writing a function map that expresses this similarity:

    map(F, [H|T]) -> [F(H)|map(F, T)];
    │ │ │ │ +map(F, [])    -> [].

    The functions double and add_one can now be expressed in terms of map as │ │ │ │ +follows:

    double(L)  -> map(fun(X) -> 2*X end, L).
    │ │ │ │ +add_one(L) -> map(fun(X) -> 1 + X end, L).

    map(F, List) is a function that takes a function F and a list L as │ │ │ │ arguments and returns a new list, obtained by applying F to each of the │ │ │ │ elements in L.

    The process of abstracting out the common features of a number of different │ │ │ │ programs is called procedural abstraction. Procedural abstraction can be used │ │ │ │ to write several different functions that have a similar structure, but differ │ │ │ │ in some minor detail. This is done as follows:

    1. Step 1. Write one function that represents the common features of these │ │ │ │ functions.
    2. Step 2. Parameterize the difference in terms of functions that are passed │ │ │ │ as arguments to the common function.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ foreach │ │ │ │

    │ │ │ │

    This section illustrates procedural abstraction. Initially, the following two │ │ │ │ -examples are written as conventional functions.

    This function prints all elements of a list onto a stream:

    print_list(Stream, [H|T]) ->
    │ │ │ │ -    io:format(Stream, "~p~n", [H]),
    │ │ │ │ -    print_list(Stream, T);
    │ │ │ │ -print_list(Stream, []) ->
    │ │ │ │ -    true.

    This function broadcasts a message to a list of processes:

    broadcast(Msg, [Pid|Pids]) ->
    │ │ │ │ +examples are written as conventional functions.

    This function prints all elements of a list onto a stream:

    print_list(Stream, [H|T]) ->
    │ │ │ │ +    io:format(Stream, "~p~n", [H]),
    │ │ │ │ +    print_list(Stream, T);
    │ │ │ │ +print_list(Stream, []) ->
    │ │ │ │ +    true.

    This function broadcasts a message to a list of processes:

    broadcast(Msg, [Pid|Pids]) ->
    │ │ │ │      Pid ! Msg,
    │ │ │ │ -    broadcast(Msg, Pids);
    │ │ │ │ -broadcast(_, []) ->
    │ │ │ │ +    broadcast(Msg, Pids);
    │ │ │ │ +broadcast(_, []) ->
    │ │ │ │      true.

    These two functions have a similar structure. They both iterate over a list and │ │ │ │ do something to each element in the list. The "something" is passed on as an │ │ │ │ -extra argument to the function that does this.

    The function foreach expresses this similarity:

    foreach(F, [H|T]) ->
    │ │ │ │ -    F(H),
    │ │ │ │ -    foreach(F, T);
    │ │ │ │ -foreach(F, []) ->
    │ │ │ │ -    ok.

    Using the function foreach, the function print_list becomes:

    foreach(fun(H) -> io:format(S, "~p~n",[H]) end, L)

    Using the function foreach, the function broadcast becomes:

    foreach(fun(Pid) -> Pid ! M end, L)

    foreach is evaluated for its side-effect and not its value. foreach(Fun ,L) │ │ │ │ +extra argument to the function that does this.

    The function foreach expresses this similarity:

    foreach(F, [H|T]) ->
    │ │ │ │ +    F(H),
    │ │ │ │ +    foreach(F, T);
    │ │ │ │ +foreach(F, []) ->
    │ │ │ │ +    ok.

    Using the function foreach, the function print_list becomes:

    foreach(fun(H) -> io:format(S, "~p~n",[H]) end, L)

    Using the function foreach, the function broadcast becomes:

    foreach(fun(Pid) -> Pid ! M end, L)

    foreach is evaluated for its side-effect and not its value. foreach(Fun ,L) │ │ │ │ calls Fun(X) for each element X in L and the processing occurs in the │ │ │ │ order that the elements were defined in L. map does not define the order in │ │ │ │ which its elements are processed.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Syntax of Funs │ │ │ │

    │ │ │ │

    Funs are written with the following syntax (see │ │ │ │ -Fun Expressions for full description):

    F = fun (Arg1, Arg2, ... ArgN) ->
    │ │ │ │ +Fun Expressions for full description):

    F = fun (Arg1, Arg2, ... ArgN) ->
    │ │ │ │          ...
    │ │ │ │      end

    This creates an anonymous function of N arguments and binds it to the variable │ │ │ │ F.

    Another function, FunctionName, written in the same module, can be passed as │ │ │ │ an argument, using the following syntax:

    F = fun FunctionName/Arity

    With this form of function reference, the function that is referred to does not │ │ │ │ need to be exported from the module.

    It is also possible to refer to a function defined in a different module, with │ │ │ │ -the following syntax:

    F = fun Module:FunctionName/Arity

    In this case, the function must be exported from the module in question.

    The following program illustrates the different ways of creating funs:

    -module(fun_test).
    │ │ │ │ --export([t1/0, t2/0]).
    │ │ │ │ --import(lists, [map/2]).
    │ │ │ │ +the following syntax:

    F = fun Module:FunctionName/Arity

    In this case, the function must be exported from the module in question.

    The following program illustrates the different ways of creating funs:

    -module(fun_test).
    │ │ │ │ +-export([t1/0, t2/0]).
    │ │ │ │ +-import(lists, [map/2]).
    │ │ │ │  
    │ │ │ │ -t1() -> map(fun(X) -> 2 * X end, [1,2,3,4,5]).
    │ │ │ │ +t1() -> map(fun(X) -> 2 * X end, [1,2,3,4,5]).
    │ │ │ │  
    │ │ │ │ -t2() -> map(fun double/1, [1,2,3,4,5]).
    │ │ │ │ +t2() -> map(fun double/1, [1,2,3,4,5]).
    │ │ │ │  
    │ │ │ │ -double(X) -> X * 2.

    The fun F can be evaluated with the following syntax:

    F(Arg1, Arg2, ..., Argn)

    To check whether a term is a fun, use the test │ │ │ │ -is_function/1 in a guard.

    Example:

    f(F, Args) when is_function(F) ->
    │ │ │ │ -   apply(F, Args);
    │ │ │ │ -f(N, _) when is_integer(N) ->
    │ │ │ │ +double(X) -> X * 2.

    The fun F can be evaluated with the following syntax:

    F(Arg1, Arg2, ..., Argn)

    To check whether a term is a fun, use the test │ │ │ │ +is_function/1 in a guard.

    Example:

    f(F, Args) when is_function(F) ->
    │ │ │ │ +   apply(F, Args);
    │ │ │ │ +f(N, _) when is_integer(N) ->
    │ │ │ │     N.

    Funs are a distinct type. The BIFs erlang:fun_info/1,2 can be used to retrieve │ │ │ │ information about a fun, and the BIF erlang:fun_to_list/1 returns a textual │ │ │ │ representation of a fun. The check_process_code/2 │ │ │ │ BIF returns true if the process contains funs that depend on the old version │ │ │ │ of a module.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Variable Bindings Within a Fun │ │ │ │

    │ │ │ │

    The scope rules for variables that occur in funs are as follows:

    • All variables that occur in the head of a fun are assumed to be "fresh" │ │ │ │ variables.
    • Variables that are defined before the fun, and that occur in function calls or │ │ │ │ -guard tests within the fun, have the values they had outside the fun.
    • Variables cannot be exported from a fun.

    The following examples illustrate these rules:

    print_list(File, List) ->
    │ │ │ │ -    {ok, Stream} = file:open(File, write),
    │ │ │ │ -    foreach(fun(X) -> io:format(Stream,"~p~n",[X]) end, List),
    │ │ │ │ -    file:close(Stream).

    Here, the variable X, defined in the head of the fun, is a new variable. The │ │ │ │ +guard tests within the fun, have the values they had outside the fun.

  • Variables cannot be exported from a fun.

The following examples illustrate these rules:

print_list(File, List) ->
│ │ │ │ +    {ok, Stream} = file:open(File, write),
│ │ │ │ +    foreach(fun(X) -> io:format(Stream,"~p~n",[X]) end, List),
│ │ │ │ +    file:close(Stream).

Here, the variable X, defined in the head of the fun, is a new variable. The │ │ │ │ variable Stream, which is used within the fun, gets its value from the │ │ │ │ file:open line.

As any variable that occurs in the head of a fun is considered a new variable, │ │ │ │ -it is equally valid to write as follows:

print_list(File, List) ->
│ │ │ │ -    {ok, Stream} = file:open(File, write),
│ │ │ │ -    foreach(fun(File) ->
│ │ │ │ -                io:format(Stream,"~p~n",[File])
│ │ │ │ -            end, List),
│ │ │ │ -    file:close(Stream).

Here, File is used as the new variable instead of X. This is not so wise │ │ │ │ +it is equally valid to write as follows:

print_list(File, List) ->
│ │ │ │ +    {ok, Stream} = file:open(File, write),
│ │ │ │ +    foreach(fun(File) ->
│ │ │ │ +                io:format(Stream,"~p~n",[File])
│ │ │ │ +            end, List),
│ │ │ │ +    file:close(Stream).

Here, File is used as the new variable instead of X. This is not so wise │ │ │ │ because code in the fun body cannot refer to the variable File, which is │ │ │ │ defined outside of the fun. Compiling this example gives the following │ │ │ │ diagnostic:

./FileName.erl:Line: Warning: variable 'File'
│ │ │ │        shadowed in 'fun'

This indicates that the variable File, which is defined inside the fun, │ │ │ │ collides with the variable File, which is defined outside the fun.

The rules for importing variables into a fun has the consequence that certain │ │ │ │ pattern matching operations must be moved into guard expressions and cannot be │ │ │ │ written in the head of the fun. For example, you might write the following code │ │ │ │ if you intend the first clause of F to be evaluated when the value of its │ │ │ │ -argument is Y:

f(...) ->
│ │ │ │ +argument is Y:

f(...) ->
│ │ │ │      Y = ...
│ │ │ │ -    map(fun(X) when X == Y ->
│ │ │ │ +    map(fun(X) when X == Y ->
│ │ │ │               ;
│ │ │ │ -           (_) ->
│ │ │ │ +           (_) ->
│ │ │ │               ...
│ │ │ │ -        end, ...)
│ │ │ │ -    ...

instead of writing the following code:

f(...) ->
│ │ │ │ +        end, ...)
│ │ │ │ +    ...

instead of writing the following code:

f(...) ->
│ │ │ │      Y = ...
│ │ │ │ -    map(fun(Y) ->
│ │ │ │ +    map(fun(Y) ->
│ │ │ │               ;
│ │ │ │ -           (_) ->
│ │ │ │ +           (_) ->
│ │ │ │               ...
│ │ │ │ -        end, ...)
│ │ │ │ +        end, ...)
│ │ │ │      ...

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Funs and Module Lists │ │ │ │

│ │ │ │

The following examples show a dialogue with the Erlang shell. All the higher │ │ │ │ order functions discussed are exported from the module lists.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ map │ │ │ │

│ │ │ │ -

lists:map/2 takes a function of one argument and a list of terms:

map(F, [H|T]) -> [F(H)|map(F, T)];
│ │ │ │ -map(F, [])    -> [].

It returns the list obtained by applying the function to every argument in the │ │ │ │ +

lists:map/2 takes a function of one argument and a list of terms:

map(F, [H|T]) -> [F(H)|map(F, T)];
│ │ │ │ +map(F, [])    -> [].

It returns the list obtained by applying the function to every argument in the │ │ │ │ list.

When a new fun is defined in the shell, the value of the fun is printed as │ │ │ │ -Fun#<erl_eval>:

> Double = fun(X) -> 2 * X end.
│ │ │ │ +Fun#<erl_eval>:

> Double = fun(X) -> 2 * X end.
│ │ │ │  #Fun<erl_eval.6.72228031>
│ │ │ │ -> lists:map(Double, [1,2,3,4,5]).
│ │ │ │ -[2,4,6,8,10]

│ │ │ │ +> lists:map(Double, [1,2,3,4,5]). │ │ │ │ +[2,4,6,8,10]

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ any │ │ │ │

│ │ │ │ -

lists:any/2 takes a predicate P of one argument and a list of terms:

any(Pred, [H|T]) ->
│ │ │ │ -    case Pred(H) of
│ │ │ │ +

lists:any/2 takes a predicate P of one argument and a list of terms:

any(Pred, [H|T]) ->
│ │ │ │ +    case Pred(H) of
│ │ │ │          true  ->  true;
│ │ │ │ -        false ->  any(Pred, T)
│ │ │ │ +        false ->  any(Pred, T)
│ │ │ │      end;
│ │ │ │ -any(Pred, []) ->
│ │ │ │ +any(Pred, []) ->
│ │ │ │      false.

A predicate is a function that returns true or false. any is true if │ │ │ │ there is a term X in the list such that P(X) is true.

A predicate Big(X) is defined, which is true if its argument is greater that │ │ │ │ -10:

> Big =  fun(X) -> if X > 10 -> true; true -> false end end.
│ │ │ │ +10:

> Big =  fun(X) -> if X > 10 -> true; true -> false end end.
│ │ │ │  #Fun<erl_eval.6.72228031>
│ │ │ │ -> lists:any(Big, [1,2,3,4]).
│ │ │ │ +> lists:any(Big, [1,2,3,4]).
│ │ │ │  false
│ │ │ │ -> lists:any(Big, [1,2,3,12,5]).
│ │ │ │ +> lists:any(Big, [1,2,3,12,5]).
│ │ │ │  true

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ all │ │ │ │

│ │ │ │ -

lists:all/2 has the same arguments as any:

all(Pred, [H|T]) ->
│ │ │ │ -    case Pred(H) of
│ │ │ │ -        true  ->  all(Pred, T);
│ │ │ │ +

lists:all/2 has the same arguments as any:

all(Pred, [H|T]) ->
│ │ │ │ +    case Pred(H) of
│ │ │ │ +        true  ->  all(Pred, T);
│ │ │ │          false ->  false
│ │ │ │      end;
│ │ │ │ -all(Pred, []) ->
│ │ │ │ -    true.

It is true if the predicate applied to all elements in the list is true.

> lists:all(Big, [1,2,3,4,12,6]).
│ │ │ │ +all(Pred, []) ->
│ │ │ │ +    true.

It is true if the predicate applied to all elements in the list is true.

> lists:all(Big, [1,2,3,4,12,6]).
│ │ │ │  false
│ │ │ │ -> lists:all(Big, [12,13,14,15]).
│ │ │ │ +> lists:all(Big, [12,13,14,15]).
│ │ │ │  true

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ foreach │ │ │ │

│ │ │ │ -

lists:foreach/2 takes a function of one argument and a list of terms:

foreach(F, [H|T]) ->
│ │ │ │ -    F(H),
│ │ │ │ -    foreach(F, T);
│ │ │ │ -foreach(F, []) ->
│ │ │ │ +

lists:foreach/2 takes a function of one argument and a list of terms:

foreach(F, [H|T]) ->
│ │ │ │ +    F(H),
│ │ │ │ +    foreach(F, T);
│ │ │ │ +foreach(F, []) ->
│ │ │ │      ok.

The function is applied to each argument in the list. foreach returns ok. It │ │ │ │ -is only used for its side-effect:

> lists:foreach(fun(X) -> io:format("~w~n",[X]) end, [1,2,3,4]).
│ │ │ │ +is only used for its side-effect:

> lists:foreach(fun(X) -> io:format("~w~n",[X]) end, [1,2,3,4]).
│ │ │ │  1
│ │ │ │  2
│ │ │ │  3
│ │ │ │  4
│ │ │ │  ok

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ foldl │ │ │ │

│ │ │ │ -

lists:foldl/3 takes a function of two arguments, an accumulator and a list:

foldl(F, Accu, [Hd|Tail]) ->
│ │ │ │ -    foldl(F, F(Hd, Accu), Tail);
│ │ │ │ -foldl(F, Accu, []) -> Accu.

The function is called with two arguments. The first argument is the successive │ │ │ │ +

lists:foldl/3 takes a function of two arguments, an accumulator and a list:

foldl(F, Accu, [Hd|Tail]) ->
│ │ │ │ +    foldl(F, F(Hd, Accu), Tail);
│ │ │ │ +foldl(F, Accu, []) -> Accu.

The function is called with two arguments. The first argument is the successive │ │ │ │ elements in the list. The second argument is the accumulator. The function must │ │ │ │ return a new accumulator, which is used the next time the function is called.

If you have a list of lists L = ["I","like","Erlang"], then you can sum the │ │ │ │ -lengths of all the strings in L as follows:

> L = ["I","like","Erlang"].
│ │ │ │ -["I","like","Erlang"]
│ │ │ │ -10> lists:foldl(fun(X, Sum) -> length(X) + Sum end, 0, L).
│ │ │ │ -11

lists:foldl/3 works like a while loop in an imperative language:

L =  ["I","like","Erlang"],
│ │ │ │ +lengths of all the strings in L as follows:

> L = ["I","like","Erlang"].
│ │ │ │ +["I","like","Erlang"]
│ │ │ │ +10> lists:foldl(fun(X, Sum) -> length(X) + Sum end, 0, L).
│ │ │ │ +11

lists:foldl/3 works like a while loop in an imperative language:

L =  ["I","like","Erlang"],
│ │ │ │  Sum = 0,
│ │ │ │ -while( L != []){
│ │ │ │ -    Sum += length(head(L)),
│ │ │ │ -    L = tail(L)
│ │ │ │ +while( L != []){
│ │ │ │ +    Sum += length(head(L)),
│ │ │ │ +    L = tail(L)
│ │ │ │  end

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ mapfoldl │ │ │ │

│ │ │ │ -

lists:mapfoldl/3 simultaneously maps and folds over a list:

mapfoldl(F, Accu0, [Hd|Tail]) ->
│ │ │ │ -    {R,Accu1} = F(Hd, Accu0),
│ │ │ │ -    {Rs,Accu2} = mapfoldl(F, Accu1, Tail),
│ │ │ │ -    {[R|Rs], Accu2};
│ │ │ │ -mapfoldl(F, Accu, []) -> {[], Accu}.

The following example shows how to change all letters in L to upper case and │ │ │ │ -then count them.

First the change to upper case:

> Upcase =  fun(X) when $a =< X,  X =< $z -> X + $A - $a;
│ │ │ │ -(X) -> X
│ │ │ │ +

lists:mapfoldl/3 simultaneously maps and folds over a list:

mapfoldl(F, Accu0, [Hd|Tail]) ->
│ │ │ │ +    {R,Accu1} = F(Hd, Accu0),
│ │ │ │ +    {Rs,Accu2} = mapfoldl(F, Accu1, Tail),
│ │ │ │ +    {[R|Rs], Accu2};
│ │ │ │ +mapfoldl(F, Accu, []) -> {[], Accu}.

The following example shows how to change all letters in L to upper case and │ │ │ │ +then count them.

First the change to upper case:

> Upcase =  fun(X) when $a =< X,  X =< $z -> X + $A - $a;
│ │ │ │ +(X) -> X
│ │ │ │  end.
│ │ │ │  #Fun<erl_eval.6.72228031>
│ │ │ │  > Upcase_word =
│ │ │ │ -fun(X) ->
│ │ │ │ -lists:map(Upcase, X)
│ │ │ │ +fun(X) ->
│ │ │ │ +lists:map(Upcase, X)
│ │ │ │  end.
│ │ │ │  #Fun<erl_eval.6.72228031>
│ │ │ │ -> Upcase_word("Erlang").
│ │ │ │ +> Upcase_word("Erlang").
│ │ │ │  "ERLANG"
│ │ │ │ -> lists:map(Upcase_word, L).
│ │ │ │ -["I","LIKE","ERLANG"]

Now, the fold and the map can be done at the same time:

> lists:mapfoldl(fun(Word, Sum) ->
│ │ │ │ -{Upcase_word(Word), Sum + length(Word)}
│ │ │ │ -end, 0, L).
│ │ │ │ -{["I","LIKE","ERLANG"],11}

│ │ │ │ +> lists:map(Upcase_word, L). │ │ │ │ +["I","LIKE","ERLANG"]

Now, the fold and the map can be done at the same time:

> lists:mapfoldl(fun(Word, Sum) ->
│ │ │ │ +{Upcase_word(Word), Sum + length(Word)}
│ │ │ │ +end, 0, L).
│ │ │ │ +{["I","LIKE","ERLANG"],11}

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ filter │ │ │ │

│ │ │ │

lists:filter/2 takes a predicate of one argument and a list and returns all elements │ │ │ │ -in the list that satisfy the predicate:

filter(F, [H|T]) ->
│ │ │ │ -    case F(H) of
│ │ │ │ -        true  -> [H|filter(F, T)];
│ │ │ │ -        false -> filter(F, T)
│ │ │ │ +in the list that satisfy the predicate:

filter(F, [H|T]) ->
│ │ │ │ +    case F(H) of
│ │ │ │ +        true  -> [H|filter(F, T)];
│ │ │ │ +        false -> filter(F, T)
│ │ │ │      end;
│ │ │ │ -filter(F, []) -> [].
> lists:filter(Big, [500,12,2,45,6,7]).
│ │ │ │ -[500,12,45]

Combining maps and filters enables writing of very succinct code. For example, │ │ │ │ +filter(F, []) -> [].

> lists:filter(Big, [500,12,2,45,6,7]).
│ │ │ │ +[500,12,45]

Combining maps and filters enables writing of very succinct code. For example, │ │ │ │ to define a set difference function diff(L1, L2) to be the difference between │ │ │ │ -the lists L1 and L2, the code can be written as follows:

diff(L1, L2) ->
│ │ │ │ -    filter(fun(X) -> not member(X, L2) end, L1).

This gives the list of all elements in L1 that are not contained in L2.

The AND intersection of the list L1 and L2 is also easily defined:

intersection(L1,L2) -> filter(fun(X) -> member(X,L1) end, L2).

│ │ │ │ +the lists L1 and L2, the code can be written as follows:

diff(L1, L2) ->
│ │ │ │ +    filter(fun(X) -> not member(X, L2) end, L1).

This gives the list of all elements in L1 that are not contained in L2.

The AND intersection of the list L1 and L2 is also easily defined:

intersection(L1,L2) -> filter(fun(X) -> member(X,L1) end, L2).

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ takewhile │ │ │ │

│ │ │ │

lists:takewhile/2 takes elements X from a list L as long as the predicate │ │ │ │ -P(X) is true:

takewhile(Pred, [H|T]) ->
│ │ │ │ -    case Pred(H) of
│ │ │ │ -        true  -> [H|takewhile(Pred, T)];
│ │ │ │ -        false -> []
│ │ │ │ +P(X) is true:

takewhile(Pred, [H|T]) ->
│ │ │ │ +    case Pred(H) of
│ │ │ │ +        true  -> [H|takewhile(Pred, T)];
│ │ │ │ +        false -> []
│ │ │ │      end;
│ │ │ │ -takewhile(Pred, []) ->
│ │ │ │ -    [].
> lists:takewhile(Big, [200,500,45,5,3,45,6]).
│ │ │ │ -[200,500,45]

│ │ │ │ +takewhile(Pred, []) -> │ │ │ │ + [].

> lists:takewhile(Big, [200,500,45,5,3,45,6]).
│ │ │ │ +[200,500,45]

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ dropwhile │ │ │ │

│ │ │ │ -

lists:dropwhile/2 is the complement of takewhile:

dropwhile(Pred, [H|T]) ->
│ │ │ │ -    case Pred(H) of
│ │ │ │ -        true  -> dropwhile(Pred, T);
│ │ │ │ -        false -> [H|T]
│ │ │ │ +

lists:dropwhile/2 is the complement of takewhile:

dropwhile(Pred, [H|T]) ->
│ │ │ │ +    case Pred(H) of
│ │ │ │ +        true  -> dropwhile(Pred, T);
│ │ │ │ +        false -> [H|T]
│ │ │ │      end;
│ │ │ │ -dropwhile(Pred, []) ->
│ │ │ │ -    [].
> lists:dropwhile(Big, [200,500,45,5,3,45,6]).
│ │ │ │ -[5,3,45,6]

│ │ │ │ +dropwhile(Pred, []) -> │ │ │ │ + [].

> lists:dropwhile(Big, [200,500,45,5,3,45,6]).
│ │ │ │ +[5,3,45,6]

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ splitwith │ │ │ │

│ │ │ │

lists:splitwith/2 splits the list L into the two sublists {L1, L2}, where │ │ │ │ -L = takewhile(P, L) and L2 = dropwhile(P, L):

splitwith(Pred, L) ->
│ │ │ │ -    splitwith(Pred, L, []).
│ │ │ │ +L = takewhile(P, L) and L2 = dropwhile(P, L):

splitwith(Pred, L) ->
│ │ │ │ +    splitwith(Pred, L, []).
│ │ │ │  
│ │ │ │ -splitwith(Pred, [H|T], L) ->
│ │ │ │ -    case Pred(H) of
│ │ │ │ -        true  -> splitwith(Pred, T, [H|L]);
│ │ │ │ -        false -> {reverse(L), [H|T]}
│ │ │ │ +splitwith(Pred, [H|T], L) ->
│ │ │ │ +    case Pred(H) of
│ │ │ │ +        true  -> splitwith(Pred, T, [H|L]);
│ │ │ │ +        false -> {reverse(L), [H|T]}
│ │ │ │      end;
│ │ │ │ -splitwith(Pred, [], L) ->
│ │ │ │ -    {reverse(L), []}.
> lists:splitwith(Big, [200,500,45,5,3,45,6]).
│ │ │ │ -{[200,500,45],[5,3,45,6]}

│ │ │ │ +splitwith(Pred, [], L) -> │ │ │ │ + {reverse(L), []}.

> lists:splitwith(Big, [200,500,45,5,3,45,6]).
│ │ │ │ +{[200,500,45],[5,3,45,6]}

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Funs Returning Funs │ │ │ │

│ │ │ │

So far, only functions that take funs as arguments have been described. More │ │ │ │ powerful functions, that themselves return funs, can also be written. The │ │ │ │ following examples illustrate these type of functions.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Simple Higher Order Functions │ │ │ │

│ │ │ │

Adder(X) is a function that given X, returns a new function G such that │ │ │ │ -G(K) returns K + X:

> Adder = fun(X) -> fun(Y) -> X + Y end end.
│ │ │ │ +G(K) returns K + X:

> Adder = fun(X) -> fun(Y) -> X + Y end end.
│ │ │ │  #Fun<erl_eval.6.72228031>
│ │ │ │ -> Add6 = Adder(6).
│ │ │ │ +> Add6 = Adder(6).
│ │ │ │  #Fun<erl_eval.6.72228031>
│ │ │ │ -> Add6(10).
│ │ │ │ +> Add6(10).
│ │ │ │  16

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Infinite Lists │ │ │ │

│ │ │ │ -

The idea is to write something like:

-module(lazy).
│ │ │ │ --export([ints_from/1]).
│ │ │ │ -ints_from(N) ->
│ │ │ │ -    fun() ->
│ │ │ │ -            [N|ints_from(N+1)]
│ │ │ │ -    end.

Then proceed as follows:

> XX = lazy:ints_from(1).
│ │ │ │ +

The idea is to write something like:

-module(lazy).
│ │ │ │ +-export([ints_from/1]).
│ │ │ │ +ints_from(N) ->
│ │ │ │ +    fun() ->
│ │ │ │ +            [N|ints_from(N+1)]
│ │ │ │ +    end.

Then proceed as follows:

> XX = lazy:ints_from(1).
│ │ │ │  #Fun<lazy.0.29874839>
│ │ │ │ -> XX().
│ │ │ │ -[1|#Fun<lazy.0.29874839>]
│ │ │ │ -> hd(XX()).
│ │ │ │ +> XX().
│ │ │ │ +[1|#Fun<lazy.0.29874839>]
│ │ │ │ +> hd(XX()).
│ │ │ │  1
│ │ │ │ -> Y = tl(XX()).
│ │ │ │ +> Y = tl(XX()).
│ │ │ │  #Fun<lazy.0.29874839>
│ │ │ │ -> hd(Y()).
│ │ │ │ +> hd(Y()).
│ │ │ │  2

And so on. This is an example of "lazy embedding".

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Parsing │ │ │ │

│ │ │ │ -

The following examples show parsers of the following type:

Parser(Toks) -> {ok, Tree, Toks1} | fail

Toks is the list of tokens to be parsed. A successful parse returns │ │ │ │ +

The following examples show parsers of the following type:

Parser(Toks) -> {ok, Tree, Toks1} | fail

Toks is the list of tokens to be parsed. A successful parse returns │ │ │ │ {ok, Tree, Toks1}.

  • Tree is a parse tree.
  • Toks1 is a tail of Tree that contains symbols encountered after the │ │ │ │ structure that was correctly parsed.

An unsuccessful parse returns fail.

The following example illustrates a simple, functional parser that parses the │ │ │ │ grammar:

(a | b) & (c | d)

The following code defines a function pconst(X) in the module funparse, │ │ │ │ -which returns a fun that parses a list of tokens:

pconst(X) ->
│ │ │ │ -    fun (T) ->
│ │ │ │ +which returns a fun that parses a list of tokens:

pconst(X) ->
│ │ │ │ +    fun (T) ->
│ │ │ │         case T of
│ │ │ │ -           [X|T1] -> {ok, {const, X}, T1};
│ │ │ │ +           [X|T1] -> {ok, {const, X}, T1};
│ │ │ │             _      -> fail
│ │ │ │         end
│ │ │ │ -    end.

This function can be used as follows:

> P1 = funparse:pconst(a).
│ │ │ │ +    end.

This function can be used as follows:

> P1 = funparse:pconst(a).
│ │ │ │  #Fun<funparse.0.22674075>
│ │ │ │ -> P1([a,b,c]).
│ │ │ │ -{ok,{const,a},[b,c]}
│ │ │ │ -> P1([x,y,z]).
│ │ │ │ +> P1([a,b,c]).
│ │ │ │ +{ok,{const,a},[b,c]}
│ │ │ │ +> P1([x,y,z]).
│ │ │ │  fail

Next, the two higher order functions pand and por are defined. They combine │ │ │ │ -primitive parsers to produce more complex parsers.

First pand:

pand(P1, P2) ->
│ │ │ │ -    fun (T) ->
│ │ │ │ -        case P1(T) of
│ │ │ │ -            {ok, R1, T1} ->
│ │ │ │ -                case P2(T1) of
│ │ │ │ -                    {ok, R2, T2} ->
│ │ │ │ -                        {ok, {'and', R1, R2}};
│ │ │ │ +primitive parsers to produce more complex parsers.

First pand:

pand(P1, P2) ->
│ │ │ │ +    fun (T) ->
│ │ │ │ +        case P1(T) of
│ │ │ │ +            {ok, R1, T1} ->
│ │ │ │ +                case P2(T1) of
│ │ │ │ +                    {ok, R2, T2} ->
│ │ │ │ +                        {ok, {'and', R1, R2}};
│ │ │ │                      fail ->
│ │ │ │                          fail
│ │ │ │                  end;
│ │ │ │              fail ->
│ │ │ │                  fail
│ │ │ │          end
│ │ │ │      end.

Given a parser P1 for grammar G1, and a parser P2 for grammar G2, │ │ │ │ pand(P1, P2) returns a parser for the grammar, which consists of sequences of │ │ │ │ tokens that satisfy G1, followed by sequences of tokens that satisfy G2.

por(P1, P2) returns a parser for the language described by the grammar G1 or │ │ │ │ -G2:

por(P1, P2) ->
│ │ │ │ -    fun (T) ->
│ │ │ │ -        case P1(T) of
│ │ │ │ -            {ok, R, T1} ->
│ │ │ │ -                {ok, {'or',1,R}, T1};
│ │ │ │ +G2:

por(P1, P2) ->
│ │ │ │ +    fun (T) ->
│ │ │ │ +        case P1(T) of
│ │ │ │ +            {ok, R, T1} ->
│ │ │ │ +                {ok, {'or',1,R}, T1};
│ │ │ │              fail ->
│ │ │ │ -                case P2(T) of
│ │ │ │ -                    {ok, R1, T1} ->
│ │ │ │ -                        {ok, {'or',2,R1}, T1};
│ │ │ │ +                case P2(T) of
│ │ │ │ +                    {ok, R1, T1} ->
│ │ │ │ +                        {ok, {'or',2,R1}, T1};
│ │ │ │                      fail ->
│ │ │ │                          fail
│ │ │ │                  end
│ │ │ │          end
│ │ │ │      end.

The original problem was to parse the grammar (a | b) & (c | d). The following │ │ │ │ -code addresses this problem:

grammar() ->
│ │ │ │ -    pand(
│ │ │ │ -         por(pconst(a), pconst(b)),
│ │ │ │ -         por(pconst(c), pconst(d))).

The following code adds a parser interface to the grammar:

parse(List) ->
│ │ │ │ -    (grammar())(List).

The parser can be tested as follows:

> funparse:parse([a,c]).
│ │ │ │ -{ok,{'and',{'or',1,{const,a}},{'or',1,{const,c}}}}
│ │ │ │ -> funparse:parse([a,d]).
│ │ │ │ -{ok,{'and',{'or',1,{const,a}},{'or',2,{const,d}}}}
│ │ │ │ -> funparse:parse([b,c]).
│ │ │ │ -{ok,{'and',{'or',2,{const,b}},{'or',1,{const,c}}}}
│ │ │ │ -> funparse:parse([b,d]).
│ │ │ │ -{ok,{'and',{'or',2,{const,b}},{'or',2,{const,d}}}}
│ │ │ │ -> funparse:parse([a,b]).
│ │ │ │ +code addresses this problem:

grammar() ->
│ │ │ │ +    pand(
│ │ │ │ +         por(pconst(a), pconst(b)),
│ │ │ │ +         por(pconst(c), pconst(d))).

The following code adds a parser interface to the grammar:

parse(List) ->
│ │ │ │ +    (grammar())(List).

The parser can be tested as follows:

> funparse:parse([a,c]).
│ │ │ │ +{ok,{'and',{'or',1,{const,a}},{'or',1,{const,c}}}}
│ │ │ │ +> funparse:parse([a,d]).
│ │ │ │ +{ok,{'and',{'or',1,{const,a}},{'or',2,{const,d}}}}
│ │ │ │ +> funparse:parse([b,c]).
│ │ │ │ +{ok,{'and',{'or',2,{const,b}},{'or',1,{const,c}}}}
│ │ │ │ +> funparse:parse([b,d]).
│ │ │ │ +{ok,{'and',{'or',2,{const,b}},{'or',2,{const,d}}}}
│ │ │ │ +> funparse:parse([a,b]).
│ │ │ │  fail
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/expressions.xhtml │ │ │ │ @@ -56,81 +56,81 @@ │ │ │ │
Phone_number │ │ │ │ _ │ │ │ │ _Height │ │ │ │ name@node

Variables are bound to values using pattern matching. Erlang uses │ │ │ │ single assignment, that is, a variable can only be bound once.

The anonymous variable is denoted by underscore (_) and can be used when a │ │ │ │ variable is required but its value can be ignored.

Example:

[H|_] = [1,2,3]

Variables starting with underscore (_), for example, _Height, are normal │ │ │ │ variables, not anonymous. However, they are ignored by the compiler in the sense │ │ │ │ -that they do not generate warnings.

Example:

The following code:

member(_, []) ->
│ │ │ │ -    [].

can be rewritten to be more readable:

member(Elem, []) ->
│ │ │ │ -    [].

This causes a warning for an unused variable, Elem. To avoid the warning, │ │ │ │ -the code can be rewritten to:

member(_Elem, []) ->
│ │ │ │ -    [].

Notice that since variables starting with an underscore are not anonymous, the │ │ │ │ -following example matches:

{_,_} = {1,2}

But this example fails:

{_N,_N} = {1,2}

The scope for a variable is its function clause. Variables bound in a branch of │ │ │ │ +that they do not generate warnings.

Example:

The following code:

member(_, []) ->
│ │ │ │ +    [].

can be rewritten to be more readable:

member(Elem, []) ->
│ │ │ │ +    [].

This causes a warning for an unused variable, Elem. To avoid the warning, │ │ │ │ +the code can be rewritten to:

member(_Elem, []) ->
│ │ │ │ +    [].

Notice that since variables starting with an underscore are not anonymous, the │ │ │ │ +following example matches:

{_,_} = {1,2}

But this example fails:

{_N,_N} = {1,2}

The scope for a variable is its function clause. Variables bound in a branch of │ │ │ │ an if, case, or receive expression must be bound in all branches to have a │ │ │ │ value outside the expression. Otherwise they are regarded as unsafe outside │ │ │ │ the expression.

For the try expression variable scoping is limited so that variables bound in │ │ │ │ the expression are always unsafe outside the expression.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Patterns │ │ │ │

│ │ │ │

A pattern has the same structure as a term but can contain unbound variables.

Example:

Name1
│ │ │ │ -[H|T]
│ │ │ │ -{error,Reason}

Patterns are allowed in clause heads, case expressions, │ │ │ │ +[H|T] │ │ │ │ +{error,Reason}

Patterns are allowed in clause heads, case expressions, │ │ │ │ receive expressions, and │ │ │ │ match expressions.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ The Compound Pattern Operator │ │ │ │

│ │ │ │

If Pattern1 and Pattern2 are valid patterns, the following is also a valid │ │ │ │ pattern:

Pattern1 = Pattern2

When matched against a term, both Pattern1 and Pattern2 are matched against │ │ │ │ -the term. The idea behind this feature is to avoid reconstruction of terms.

Example:

f({connect,From,To,Number,Options}, To) ->
│ │ │ │ -    Signal = {connect,From,To,Number,Options},
│ │ │ │ +the term. The idea behind this feature is to avoid reconstruction of terms.

Example:

f({connect,From,To,Number,Options}, To) ->
│ │ │ │ +    Signal = {connect,From,To,Number,Options},
│ │ │ │      ...;
│ │ │ │ -f(Signal, To) ->
│ │ │ │ -    ignore.

can instead be written as

f({connect,_,To,_,_} = Signal, To) ->
│ │ │ │ +f(Signal, To) ->
│ │ │ │ +    ignore.

can instead be written as

f({connect,_,To,_,_} = Signal, To) ->
│ │ │ │      ...;
│ │ │ │ -f(Signal, To) ->
│ │ │ │ +f(Signal, To) ->
│ │ │ │      ignore.

The compound pattern operator does not imply that its operands are matched in │ │ │ │ any particular order. That means that it is not legal to bind a variable in │ │ │ │ Pattern1 and use it in Pattern2, or vice versa.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ String Prefix in Patterns │ │ │ │

│ │ │ │ -

When matching strings, the following is a valid pattern:

f("prefix" ++ Str) -> ...

This is syntactic sugar for the equivalent, but harder to read:

f([$p,$r,$e,$f,$i,$x | Str]) -> ...

│ │ │ │ +

When matching strings, the following is a valid pattern:

f("prefix" ++ Str) -> ...

This is syntactic sugar for the equivalent, but harder to read:

f([$p,$r,$e,$f,$i,$x | Str]) -> ...

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Expressions in Patterns │ │ │ │

│ │ │ │

An arithmetic expression can be used within a pattern if it meets both of the │ │ │ │ -following two conditions:

  • It uses only numeric or bitwise operators.
  • Its value can be evaluated to a constant when complied.

Example:

case {Value, Result} of
│ │ │ │ -    {?THRESHOLD+1, ok} -> ...

│ │ │ │ +following two conditions:

  • It uses only numeric or bitwise operators.
  • Its value can be evaluated to a constant when complied.

Example:

case {Value, Result} of
│ │ │ │ +    {?THRESHOLD+1, ok} -> ...

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ The Match Operator │ │ │ │

│ │ │ │

The following matches Pattern against Expr:

Pattern = Expr

If the matching succeeds, any unbound variable in the pattern becomes bound and │ │ │ │ the value of Expr is returned.

If multiple match operators are applied in sequence, they will be evaluated from │ │ │ │ -right to left.

If the matching fails, a badmatch run-time error occurs.

Examples:

1> {A, B} = T = {answer, 42}.
│ │ │ │ -{answer,42}
│ │ │ │ +right to left.

If the matching fails, a badmatch run-time error occurs.

Examples:

1> {A, B} = T = {answer, 42}.
│ │ │ │ +{answer,42}
│ │ │ │  2> A.
│ │ │ │  answer
│ │ │ │  3> B.
│ │ │ │  42
│ │ │ │  4> T.
│ │ │ │ -{answer,42}
│ │ │ │ -5> {C, D} = [1, 2].
│ │ │ │ +{answer,42}
│ │ │ │ +5> {C, D} = [1, 2].
│ │ │ │  ** exception error: no match of right-hand side value [1,2]

Because multiple match operators are evaluated from right to left, it means │ │ │ │ that:

Pattern1 = Pattern2 = . . . = PatternN = Expression

is equivalent to:

Temporary = Expression,
│ │ │ │  PatternN = Temporary,
│ │ │ │     .
│ │ │ │     .
│ │ │ │     .,
│ │ │ │  Pattern2 = Temporary,
│ │ │ │ @@ -144,30 +144,30 @@
│ │ │ │  can safely be skipped on a first reading.

The = character is used to denote two similar but distinct operators: the │ │ │ │ match operator and the compound pattern operator. Which one is meant is │ │ │ │ determined by context.

The compound pattern operator is used to construct a compound pattern from two │ │ │ │ patterns. Compound patterns are accepted everywhere a pattern is accepted. A │ │ │ │ compound pattern matches if all of its constituent patterns match. It is not │ │ │ │ legal for a pattern that is part of a compound pattern to use variables (as keys │ │ │ │ in map patterns or sizes in binary patterns) bound in other sub patterns of the │ │ │ │ -same compound pattern.

Examples:

1> fun(#{Key := Value} = #{key := Key}) -> Value end.
│ │ │ │ +same compound pattern.

Examples:

1> fun(#{Key := Value} = #{key := Key}) -> Value end.
│ │ │ │  * 1:7: variable 'Key' is unbound
│ │ │ │ -2> F = fun({A, B} = E) -> {E, A + B} end, F({1,2}).
│ │ │ │ -{{1,2},3}
│ │ │ │ -3> G = fun(<<A:8,B:8>> = <<C:16>>) -> {A, B, C} end, G(<<42,43>>).
│ │ │ │ -{42,43,10795}

The match operator is allowed everywhere an expression is allowed. It is used │ │ │ │ +2> F = fun({A, B} = E) -> {E, A + B} end, F({1,2}). │ │ │ │ +{{1,2},3} │ │ │ │ +3> G = fun(<<A:8,B:8>> = <<C:16>>) -> {A, B, C} end, G(<<42,43>>). │ │ │ │ +{42,43,10795}

The match operator is allowed everywhere an expression is allowed. It is used │ │ │ │ to match the value of an expression to a pattern. If multiple match operators │ │ │ │ -are applied in sequence, they will be evaluated from right to left.

Examples:

1> M = #{key => key2, key2 => value}.
│ │ │ │ -#{key => key2,key2 => value}
│ │ │ │ -2> f(Key), #{Key := Value} = #{key := Key} = M, Value.
│ │ │ │ +are applied in sequence, they will be evaluated from right to left.

Examples:

1> M = #{key => key2, key2 => value}.
│ │ │ │ +#{key => key2,key2 => value}
│ │ │ │ +2> f(Key), #{Key := Value} = #{key := Key} = M, Value.
│ │ │ │  value
│ │ │ │ -3> f(Key), #{Key := Value} = (#{key := Key} = M), Value.
│ │ │ │ +3> f(Key), #{Key := Value} = (#{key := Key} = M), Value.
│ │ │ │  value
│ │ │ │ -4> f(Key), (#{Key := Value} = #{key := Key}) = M, Value.
│ │ │ │ +4> f(Key), (#{Key := Value} = #{key := Key}) = M, Value.
│ │ │ │  * 1:12: variable 'Key' is unbound
│ │ │ │ -5> <<X:Y>> = begin Y = 8, <<42:8>> end, X.
│ │ │ │ +5> <<X:Y>> = begin Y = 8, <<42:8>> end, X.
│ │ │ │  42

The expression at prompt 2> first matches the value of variable M against │ │ │ │ pattern #{key := Key}, binding variable Key. It then matches the value of │ │ │ │ M against pattern #{Key := Value} using variable Key as the key, binding │ │ │ │ variable Value.

The expression at prompt 3> matches expression (#{key := Key} = M) against │ │ │ │ pattern #{Key := Value}. The expression inside the parentheses is evaluated │ │ │ │ first. That is, M is matched against #{key := Key}, and then the value of │ │ │ │ M is matched against pattern #{Key := Value}. That is the same evaluation │ │ │ │ @@ -181,30 +181,30 @@ │ │ │ │ binding variable Y and creating a binary. The binary is then matched against │ │ │ │ pattern <<X:Y>> using the value of Y as the size of the segment.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Function Calls │ │ │ │

│ │ │ │ -
ExprF(Expr1,...,ExprN)
│ │ │ │ -ExprM:ExprF(Expr1,...,ExprN)

In the first form of function calls, ExprM:ExprF(Expr1,...,ExprN), each of │ │ │ │ +

ExprF(Expr1,...,ExprN)
│ │ │ │ +ExprM:ExprF(Expr1,...,ExprN)

In the first form of function calls, ExprM:ExprF(Expr1,...,ExprN), each of │ │ │ │ ExprM and ExprF must be an atom or an expression that evaluates to an atom. │ │ │ │ The function is said to be called by using the fully qualified function name. │ │ │ │ -This is often referred to as a remote or external function call.

Example:

lists:keyfind(Name, 1, List)

In the second form of function calls, ExprF(Expr1,...,ExprN), ExprF must be │ │ │ │ +This is often referred to as a remote or external function call.

Example:

lists:keyfind(Name, 1, List)

In the second form of function calls, ExprF(Expr1,...,ExprN), ExprF must be │ │ │ │ an atom or evaluate to a fun.

If ExprF is an atom, the function is said to be called by using the │ │ │ │ implicitly qualified function name. If the function ExprF is locally │ │ │ │ defined, it is called. Alternatively, if ExprF is explicitly imported from the │ │ │ │ M module, M:ExprF(Expr1,...,ExprN) is called. If ExprF is neither declared │ │ │ │ locally nor explicitly imported, ExprF must be the name of an automatically │ │ │ │ -imported BIF.

Examples:

handle(Msg, State)
│ │ │ │ -spawn(m, init, [])

Examples where ExprF is a fun:

1> Fun1 = fun(X) -> X+1 end,
│ │ │ │ -Fun1(3).
│ │ │ │ +imported BIF.

Examples:

handle(Msg, State)
│ │ │ │ +spawn(m, init, [])

Examples where ExprF is a fun:

1> Fun1 = fun(X) -> X+1 end,
│ │ │ │ +Fun1(3).
│ │ │ │  4
│ │ │ │ -2> fun lists:append/2([1,2], [3,4]).
│ │ │ │ -[1,2,3,4]
│ │ │ │ +2> fun lists:append/2([1,2], [3,4]).
│ │ │ │ +[1,2,3,4]
│ │ │ │  3>

Notice that when calling a local function, there is a difference between using │ │ │ │ the implicitly or fully qualified function name. The latter always refers to the │ │ │ │ latest version of the module. See │ │ │ │ Compilation and Code Loading and │ │ │ │ Function Evaluation.

│ │ │ │ │ │ │ │ │ │ │ │ @@ -221,40 +221,40 @@ │ │ │ │ called instead. This is to avoid that future additions to the set of │ │ │ │ auto-imported BIFs do not silently change the behavior of old code.

However, to avoid that old (pre R14) code changed its behavior when compiled │ │ │ │ with Erlang/OTP version R14A or later, the following restriction applies: If you │ │ │ │ override the name of a BIF that was auto-imported in OTP versions prior to R14A │ │ │ │ (ERTS version 5.8) and have an implicitly qualified call to that function in │ │ │ │ your code, you either need to explicitly remove the auto-import using a compiler │ │ │ │ directive, or replace the call with a fully qualified function call. Otherwise │ │ │ │ -you get a compilation error. See the following example:

-export([length/1,f/1]).
│ │ │ │ +you get a compilation error. See the following example:

-export([length/1,f/1]).
│ │ │ │  
│ │ │ │ --compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
│ │ │ │ +-compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
│ │ │ │  
│ │ │ │ -length([]) ->
│ │ │ │ +length([]) ->
│ │ │ │      0;
│ │ │ │ -length([H|T]) ->
│ │ │ │ -    1 + length(T). %% Calls the local function length/1
│ │ │ │ +length([H|T]) ->
│ │ │ │ +    1 + length(T). %% Calls the local function length/1
│ │ │ │  
│ │ │ │ -f(X) when erlang:length(X) > 3 -> %% Calls erlang:length/1,
│ │ │ │ +f(X) when erlang:length(X) > 3 -> %% Calls erlang:length/1,
│ │ │ │                                    %% which is allowed in guards
│ │ │ │      long.

The same logic applies to explicitly imported functions from other modules, as │ │ │ │ to locally defined functions. It is not allowed to both import a function from │ │ │ │ -another module and have the function declared in the module at the same time:

-export([f/1]).
│ │ │ │ +another module and have the function declared in the module at the same time:

-export([f/1]).
│ │ │ │  
│ │ │ │ --compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
│ │ │ │ +-compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
│ │ │ │  
│ │ │ │ --import(mod,[length/1]).
│ │ │ │ +-import(mod,[length/1]).
│ │ │ │  
│ │ │ │ -f(X) when erlang:length(X) > 33 -> %% Calls erlang:length/1,
│ │ │ │ +f(X) when erlang:length(X) > 33 -> %% Calls erlang:length/1,
│ │ │ │                                     %% which is allowed in guards
│ │ │ │  
│ │ │ │ -    erlang:length(X);              %% Explicit call to erlang:length in body
│ │ │ │ +    erlang:length(X);              %% Explicit call to erlang:length in body
│ │ │ │  
│ │ │ │ -f(X) ->
│ │ │ │ -    length(X).                     %% mod:length/1 is called

For auto-imported BIFs added in Erlang/OTP R14A and thereafter, overriding the │ │ │ │ +f(X) -> │ │ │ │ + length(X). %% mod:length/1 is called

For auto-imported BIFs added in Erlang/OTP R14A and thereafter, overriding the │ │ │ │ name with a local function or explicit import is always allowed. However, if the │ │ │ │ -compile({no_auto_import,[F/A]) directive is not used, the compiler issues a │ │ │ │ warning whenever the function is called in the module using the implicitly │ │ │ │ qualified function name.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -266,40 +266,40 @@ │ │ │ │ ...; │ │ │ │ GuardSeqN -> │ │ │ │ BodyN │ │ │ │ end

The branches of an if-expression are scanned sequentially until a guard │ │ │ │ sequence GuardSeq that evaluates to true is found. Then the corresponding │ │ │ │ Body (a sequence of expressions separated by ,) is evaluated.

The return value of Body is the return value of the if expression.

If no guard sequence is evaluated as true, an if_clause run-time error occurs. │ │ │ │ If necessary, the guard expression true can be used in the last branch, as │ │ │ │ -that guard sequence is always true.

Example:

is_greater_than(X, Y) ->
│ │ │ │ +that guard sequence is always true.

Example:

is_greater_than(X, Y) ->
│ │ │ │      if
│ │ │ │          X > Y ->
│ │ │ │              true;
│ │ │ │          true -> % works as an 'else' branch
│ │ │ │              false
│ │ │ │      end

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Case │ │ │ │

│ │ │ │
case Expr of
│ │ │ │ -    Pattern1 [when GuardSeq1] ->
│ │ │ │ +    Pattern1 [when GuardSeq1] ->
│ │ │ │          Body1;
│ │ │ │      ...;
│ │ │ │ -    PatternN [when GuardSeqN] ->
│ │ │ │ +    PatternN [when GuardSeqN] ->
│ │ │ │          BodyN
│ │ │ │  end

The expression Expr is evaluated and the patterns Pattern are sequentially │ │ │ │ matched against the result. If a match succeeds and the optional guard sequence │ │ │ │ GuardSeq is true, the corresponding Body is evaluated.

The return value of Body is the return value of the case expression.

If there is no matching pattern with a true guard sequence, a case_clause │ │ │ │ -run-time error occurs.

Example:

is_valid_signal(Signal) ->
│ │ │ │ +run-time error occurs.

Example:

is_valid_signal(Signal) ->
│ │ │ │      case Signal of
│ │ │ │ -        {signal, _What, _From, _To} ->
│ │ │ │ +        {signal, _What, _From, _To} ->
│ │ │ │              true;
│ │ │ │ -        {signal, _What, _To} ->
│ │ │ │ +        {signal, _What, _To} ->
│ │ │ │              true;
│ │ │ │          _Else ->
│ │ │ │              false
│ │ │ │      end.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -317,57 +317,57 @@ │ │ │ │ the top-level of a maybe block. It matches the pattern Expr1 against │ │ │ │ Expr2. If the matching succeeds, any unbound variable in the pattern becomes │ │ │ │ bound. If the expression is the last expression in the maybe block, it also │ │ │ │ returns the value of Expr2. If the matching is unsuccessful, the rest of the │ │ │ │ expressions in the maybe block are skipped and the return value of the maybe │ │ │ │ block is Expr2.

None of the variables bound in a maybe block must be used in the code that │ │ │ │ follows the block.

Here is an example:

maybe
│ │ │ │ -    {ok, A} ?= a(),
│ │ │ │ +    {ok, A} ?= a(),
│ │ │ │      true = A >= 0,
│ │ │ │ -    {ok, B} ?= b(),
│ │ │ │ +    {ok, B} ?= b(),
│ │ │ │      A + B
│ │ │ │  end

Let us first assume that a() returns {ok,42} and b() returns {ok,58}. │ │ │ │ With those return values, all of the match operators will succeed, and the │ │ │ │ return value of the maybe block is A + B, which is equal to 42 + 58 = 100.

Now let us assume that a() returns error. The conditional match operator in │ │ │ │ {ok, A} ?= a() fails to match, and the return value of the maybe block is │ │ │ │ the value of the expression that failed to match, namely error. Similarly, if │ │ │ │ b() returns wrong, the return value of the maybe block is wrong.

Finally, let us assume that a() returns {ok,-1}. Because true = A >= 0 uses │ │ │ │ the match operator =, a {badmatch,false} run-time error occurs when the │ │ │ │ -expression fails to match the pattern.

The example can be written in a less succinct way using nested case expressions:

case a() of
│ │ │ │ -    {ok, A} ->
│ │ │ │ +expression fails to match the pattern.

The example can be written in a less succinct way using nested case expressions:

case a() of
│ │ │ │ +    {ok, A} ->
│ │ │ │          true = A >= 0,
│ │ │ │ -        case b() of
│ │ │ │ -            {ok, B} ->
│ │ │ │ +        case b() of
│ │ │ │ +            {ok, B} ->
│ │ │ │                  A + B;
│ │ │ │              Other1 ->
│ │ │ │                  Other1
│ │ │ │          end;
│ │ │ │      Other2 ->
│ │ │ │          Other2
│ │ │ │  end

The maybe block can be augmented with else clauses:

maybe
│ │ │ │      Expr1,
│ │ │ │      ...,
│ │ │ │      ExprN
│ │ │ │  else
│ │ │ │ -    Pattern1 [when GuardSeq1] ->
│ │ │ │ +    Pattern1 [when GuardSeq1] ->
│ │ │ │          Body1;
│ │ │ │      ...;
│ │ │ │ -    PatternN [when GuardSeqN] ->
│ │ │ │ +    PatternN [when GuardSeqN] ->
│ │ │ │          BodyN
│ │ │ │  end

If a conditional match operator fails, the failed expression is matched against │ │ │ │ the patterns in all clauses between the else and end keywords. If a match │ │ │ │ succeeds and the optional guard sequence GuardSeq is true, the corresponding │ │ │ │ Body is evaluated. The value returned from the body is the return value of the │ │ │ │ maybe block.

If there is no matching pattern with a true guard sequence, an else_clause │ │ │ │ run-time error occurs.

None of the variables bound in a maybe block must be used in the else │ │ │ │ clauses. None of the variables bound in the else clauses must be used in the │ │ │ │ code that follows the maybe block.

Here is the previous example augmented with else clauses:

maybe
│ │ │ │ -    {ok, A} ?= a(),
│ │ │ │ +    {ok, A} ?= a(),
│ │ │ │      true = A >= 0,
│ │ │ │ -    {ok, B} ?= b(),
│ │ │ │ +    {ok, B} ?= b(),
│ │ │ │      A + B
│ │ │ │  else
│ │ │ │      error -> error;
│ │ │ │      wrong -> error
│ │ │ │  end

The else clauses translate the failing value from the conditional match │ │ │ │ operators to the value error. If the failing value is not one of the │ │ │ │ recognized values, a else_clause run-time error occurs.

│ │ │ │ @@ -386,18 +386,18 @@ │ │ │ │ {Name,Node} (or a pid located at another node), also never fails.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Receive │ │ │ │

│ │ │ │
receive
│ │ │ │ -    Pattern1 [when GuardSeq1] ->
│ │ │ │ +    Pattern1 [when GuardSeq1] ->
│ │ │ │          Body1;
│ │ │ │      ...;
│ │ │ │ -    PatternN [when GuardSeqN] ->
│ │ │ │ +    PatternN [when GuardSeqN] ->
│ │ │ │          BodyN
│ │ │ │  end

The receive expression searches for a message in the message queue that match │ │ │ │ one of the patterns in the clauses of the receive expression. The patterns in │ │ │ │ the clauses is matched against a message from top to bottom. The first message, │ │ │ │ from the start of the message queue, that matches will be selected. Messages are │ │ │ │ normally │ │ │ │ enqueued in the message queue in │ │ │ │ @@ -414,27 +414,27 @@ │ │ │ │ specific messages and the message queue is huge, executing such a receive │ │ │ │ expression might become very expensive.

One type of receive expressions matching on only specific patterns can, │ │ │ │ however, be optimized by the compiler and runtime system. This in the scenario │ │ │ │ where you create a reference and │ │ │ │ match on it in all clauses of a receive expression close to where the │ │ │ │ reference was created. In this case only the amount of messages received after │ │ │ │ the reference was created needs to be inspected. For more information see the │ │ │ │ -Fetching Received Messages section of the Efficiency Guide.

Example:

wait_for_onhook() ->
│ │ │ │ +Fetching Received Messages section of the Efficiency Guide.

Example:

wait_for_onhook() ->
│ │ │ │      receive
│ │ │ │          onhook ->
│ │ │ │ -            disconnect(),
│ │ │ │ -            idle();
│ │ │ │ -        {connect, B} ->
│ │ │ │ -            B ! {busy, self()},
│ │ │ │ -            wait_for_onhook()
│ │ │ │ +            disconnect(),
│ │ │ │ +            idle();
│ │ │ │ +        {connect, B} ->
│ │ │ │ +            B ! {busy, self()},
│ │ │ │ +            wait_for_onhook()
│ │ │ │      end.

The receive expression can be augmented with a timeout:

receive
│ │ │ │ -    Pattern1 [when GuardSeq1] ->
│ │ │ │ +    Pattern1 [when GuardSeq1] ->
│ │ │ │          Body1;
│ │ │ │      ...;
│ │ │ │ -    PatternN [when GuardSeqN] ->
│ │ │ │ +    PatternN [when GuardSeqN] ->
│ │ │ │          BodyN
│ │ │ │  after
│ │ │ │      ExprT ->
│ │ │ │          BodyT
│ │ │ │  end

receive...after works exactly as receive, except that if no matching message │ │ │ │ has arrived within ExprT milliseconds, then BodyT is evaluated instead. The │ │ │ │ return value of BodyT then becomes the return value of the receive...after │ │ │ │ @@ -445,35 +445,35 @@ │ │ │ │ another short timeout) might be cheap since the timeout is short. This is │ │ │ │ not necessarily the case. If the patterns in the clauses of the receive │ │ │ │ expression only match specific messages and no such messages exist in the │ │ │ │ message queue, the whole message queue needs to be inspected before the │ │ │ │ timeout can occur. That is, the same apply as in │ │ │ │ the warning above.

The atom infinity will make the process wait indefinitely for a matching │ │ │ │ message. This is the same as not using a timeout. It can be useful for timeout │ │ │ │ -values that are calculated at runtime.

Example:

wait_for_onhook() ->
│ │ │ │ +values that are calculated at runtime.

Example:

wait_for_onhook() ->
│ │ │ │      receive
│ │ │ │          onhook ->
│ │ │ │ -            disconnect(),
│ │ │ │ -            idle();
│ │ │ │ -        {connect, B} ->
│ │ │ │ -            B ! {busy, self()},
│ │ │ │ -            wait_for_onhook()
│ │ │ │ +            disconnect(),
│ │ │ │ +            idle();
│ │ │ │ +        {connect, B} ->
│ │ │ │ +            B ! {busy, self()},
│ │ │ │ +            wait_for_onhook()
│ │ │ │      after
│ │ │ │          60000 ->
│ │ │ │ -            disconnect(),
│ │ │ │ -            error()
│ │ │ │ +            disconnect(),
│ │ │ │ +            error()
│ │ │ │      end.

It is legal to use a receive...after expression with no branches:

receive
│ │ │ │  after
│ │ │ │      ExprT ->
│ │ │ │          BodyT
│ │ │ │  end

This construction does not consume any messages, only suspends execution in the │ │ │ │ -process for ExprT milliseconds. This can be used to implement simple timers.

Example:

timer() ->
│ │ │ │ -    spawn(m, timer, [self()]).
│ │ │ │ +process for ExprT milliseconds. This can be used to implement simple timers.

Example:

timer() ->
│ │ │ │ +    spawn(m, timer, [self()]).
│ │ │ │  
│ │ │ │ -timer(Pid) ->
│ │ │ │ +timer(Pid) ->
│ │ │ │      receive
│ │ │ │      after
│ │ │ │          5000 ->
│ │ │ │              Pid ! timeout
│ │ │ │      end.

For more information on timers in Erlang in general, see the │ │ │ │ Timers section of the │ │ │ │ Time and Time Correction in Erlang │ │ │ │ @@ -515,21 +515,21 @@ │ │ │ │ false │ │ │ │ 4> 0.0 =:= -0.0. │ │ │ │ false │ │ │ │ 5> 0.0 =:= +0.0. │ │ │ │ true │ │ │ │ 6> 1 > a. │ │ │ │ false │ │ │ │ -7> #{c => 3} > #{a => 1, b => 2}. │ │ │ │ +7> #{c => 3} > #{a => 1, b => 2}. │ │ │ │ false │ │ │ │ -8> #{a => 1, b => 2} == #{a => 1.0, b => 2.0}. │ │ │ │ +8> #{a => 1, b => 2} == #{a => 1.0, b => 2.0}. │ │ │ │ true │ │ │ │ -9> <<2:2>> < <<128>>. │ │ │ │ +9> <<2:2>> < <<128>>. │ │ │ │ true │ │ │ │ -10> <<3:2>> < <<128>>. │ │ │ │ +10> <<3:2>> < <<128>>. │ │ │ │ false

Note

Prior to OTP 27, the term equivalence operators considered 0.0 │ │ │ │ and -0.0 to be the same term.

This was changed in OTP 27 but legacy code may have expected them to be │ │ │ │ considered the same. To help users catch errors that may arise from an │ │ │ │ upgrade, the compiler raises a warning when 0.0 is pattern-matched or used │ │ │ │ in a term equivalence test.

If you need to match 0.0 specifically, the warning can be silenced by │ │ │ │ writing +0.0 instead, which produces the same term but makes the compiler │ │ │ │ interpret the match as being done on purpose.

│ │ │ │ @@ -555,15 +555,15 @@ │ │ │ │ 0 │ │ │ │ 8> 2#10 bor 2#01. │ │ │ │ 3 │ │ │ │ 9> a + 10. │ │ │ │ ** exception error: an error occurred when evaluating an arithmetic expression │ │ │ │ in operator +/2 │ │ │ │ called as a + 10 │ │ │ │ -10> 1 bsl (1 bsl 64). │ │ │ │ +10> 1 bsl (1 bsl 64). │ │ │ │ ** exception error: a system limit has been reached │ │ │ │ in operator bsl/2 │ │ │ │ called as 1 bsl 18446744073709551616

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Boolean Expressions │ │ │ │ @@ -582,136 +582,136 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Short-Circuit Expressions │ │ │ │

│ │ │ │
Expr1 orelse Expr2
│ │ │ │  Expr1 andalso Expr2

Expr2 is evaluated only if necessary. That is, Expr2 is evaluated only if:

  • Expr1 evaluates to false in an orelse expression.

or

  • Expr1 evaluates to true in an andalso expression.

Returns either the value of Expr1 (that is, true or false) or the value of │ │ │ │ -Expr2 (if Expr2 is evaluated).

Example 1:

case A >= -1.0 andalso math:sqrt(A+1) > B of

This works even if A is less than -1.0, since in that case, math:sqrt/1 is │ │ │ │ -never evaluated.

Example 2:

OnlyOne = is_atom(L) orelse
│ │ │ │ -         (is_list(L) andalso length(L) == 1),

Expr2 is not required to evaluate to a Boolean value. Because of that, │ │ │ │ -andalso and orelse are tail-recursive.

Example 3 (tail-recursive function):

all(Pred, [Hd|Tail]) ->
│ │ │ │ -    Pred(Hd) andalso all(Pred, Tail);
│ │ │ │ -all(_, []) ->
│ │ │ │ +Expr2 (if Expr2 is evaluated).

Example 1:

case A >= -1.0 andalso math:sqrt(A+1) > B of

This works even if A is less than -1.0, since in that case, math:sqrt/1 is │ │ │ │ +never evaluated.

Example 2:

OnlyOne = is_atom(L) orelse
│ │ │ │ +         (is_list(L) andalso length(L) == 1),

Expr2 is not required to evaluate to a Boolean value. Because of that, │ │ │ │ +andalso and orelse are tail-recursive.

Example 3 (tail-recursive function):

all(Pred, [Hd|Tail]) ->
│ │ │ │ +    Pred(Hd) andalso all(Pred, Tail);
│ │ │ │ +all(_, []) ->
│ │ │ │      true.

Change

Before Erlang/OTP R13A, Expr2 was required to evaluate to a Boolean value, │ │ │ │ and as consequence, andalso and orelse were not tail-recursive.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ List Operations │ │ │ │

│ │ │ │
Expr1 ++ Expr2
│ │ │ │  Expr1 -- Expr2

The list concatenation operator ++ appends its second argument to its first │ │ │ │ and returns the resulting list.

The list subtraction operator -- produces a list that is a copy of the first │ │ │ │ argument. The procedure is as follows: for each element in the second argument, │ │ │ │ -the first occurrence of this element (if any) is removed.

Example:

1> [1,2,3] ++ [4,5].
│ │ │ │ -[1,2,3,4,5]
│ │ │ │ -2> [1,2,3,2,1,2] -- [2,1,2].
│ │ │ │ -[3,1,2]

│ │ │ │ +the first occurrence of this element (if any) is removed.

Example:

1> [1,2,3] ++ [4,5].
│ │ │ │ +[1,2,3,4,5]
│ │ │ │ +2> [1,2,3,2,1,2] -- [2,1,2].
│ │ │ │ +[3,1,2]

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Map Expressions │ │ │ │

│ │ │ │

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Creating Maps │ │ │ │

│ │ │ │

Constructing a new map is done by letting an expression K be associated with │ │ │ │ -another expression V:

#{K => V}

New maps can include multiple associations at construction by listing every │ │ │ │ -association:

#{K1 => V1, ..., Kn => Vn}

An empty map is constructed by not associating any terms with each other:

#{}

All keys and values in the map are terms. Any expression is first evaluated and │ │ │ │ +another expression V:

#{K => V}

New maps can include multiple associations at construction by listing every │ │ │ │ +association:

#{K1 => V1, ..., Kn => Vn}

An empty map is constructed by not associating any terms with each other:

#{}

All keys and values in the map are terms. Any expression is first evaluated and │ │ │ │ then the resulting terms are used as key and value respectively.

Keys and values are separated by the => arrow and associations are separated │ │ │ │ -by a comma (,).

Examples:

M0 = #{},                 % empty map
│ │ │ │ -M1 = #{a => <<"hello">>}, % single association with literals
│ │ │ │ -M2 = #{1 => 2, b => b},   % multiple associations with literals
│ │ │ │ -M3 = #{k => {A,B}},       % single association with variables
│ │ │ │ -M4 = #{{"w", 1} => f()}.  % compound key associated with an evaluated expression

Here, A and B are any expressions and M0 through M4 are the resulting │ │ │ │ -map terms.

If two matching keys are declared, the latter key takes precedence.

Example:

1> #{1 => a, 1 => b}.
│ │ │ │ -#{1 => b }
│ │ │ │ -2> #{1.0 => a, 1 => b}.
│ │ │ │ -#{1 => b, 1.0 => a}

The order in which the expressions constructing the keys (and their associated │ │ │ │ +by a comma (,).

Examples:

M0 = #{},                 % empty map
│ │ │ │ +M1 = #{a => <<"hello">>}, % single association with literals
│ │ │ │ +M2 = #{1 => 2, b => b},   % multiple associations with literals
│ │ │ │ +M3 = #{k => {A,B}},       % single association with variables
│ │ │ │ +M4 = #{{"w", 1} => f()}.  % compound key associated with an evaluated expression

Here, A and B are any expressions and M0 through M4 are the resulting │ │ │ │ +map terms.

If two matching keys are declared, the latter key takes precedence.

Example:

1> #{1 => a, 1 => b}.
│ │ │ │ +#{1 => b }
│ │ │ │ +2> #{1.0 => a, 1 => b}.
│ │ │ │ +#{1 => b, 1.0 => a}

The order in which the expressions constructing the keys (and their associated │ │ │ │ values) are evaluated is not defined. The syntactic order of the key-value pairs │ │ │ │ in the construction is of no relevance, except in the recently mentioned case of │ │ │ │ two matching keys.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Updating Maps │ │ │ │

│ │ │ │

Updating a map has a similar syntax as constructing it.

An expression defining the map to be updated is put in front of the expression │ │ │ │ -defining the keys to be updated and their respective values:

M#{K => V}

Here M is a term of type map and K and V are any expression.

If key K does not match any existing key in the map, a new association is │ │ │ │ +defining the keys to be updated and their respective values:

M#{K => V}

Here M is a term of type map and K and V are any expression.

If key K does not match any existing key in the map, a new association is │ │ │ │ created from key K to value V.

If key K matches an existing key in map M, its associated value is replaced │ │ │ │ by the new value V. In both cases, the evaluated map expression returns a new │ │ │ │ -map.

If M is not of type map, an exception of type badmap is raised.

To only update an existing value, the following syntax is used:

M#{K := V}

Here M is a term of type map, V is an expression and K is an expression │ │ │ │ +map.

If M is not of type map, an exception of type badmap is raised.

To only update an existing value, the following syntax is used:

M#{K := V}

Here M is a term of type map, V is an expression and K is an expression │ │ │ │ that evaluates to an existing key in M.

If key K does not match any existing keys in map M, an exception of type │ │ │ │ badkey is raised at runtime. If a matching key K is present in map M, │ │ │ │ its associated value is replaced by the new value V, and the evaluated map │ │ │ │ -expression returns a new map.

If M is not of type map, an exception of type badmap is raised.

Examples:

M0 = #{},
│ │ │ │ -M1 = M0#{a => 0},
│ │ │ │ -M2 = M1#{a => 1, b => 2},
│ │ │ │ -M3 = M2#{"function" => fun() -> f() end},
│ │ │ │ -M4 = M3#{a := 2, b := 3}.  % 'a' and 'b' was added in `M1` and `M2`.

Here M0 is any map. It follows that M1 through M4 are maps as well.

More examples:

1> M = #{1 => a}.
│ │ │ │ -#{1 => a }
│ │ │ │ -2> M#{1.0 => b}.
│ │ │ │ -#{1 => a, 1.0 => b}.
│ │ │ │ -3> M#{1 := b}.
│ │ │ │ -#{1 => b}
│ │ │ │ -4> M#{1.0 := b}.
│ │ │ │ +expression returns a new map.

If M is not of type map, an exception of type badmap is raised.

Examples:

M0 = #{},
│ │ │ │ +M1 = M0#{a => 0},
│ │ │ │ +M2 = M1#{a => 1, b => 2},
│ │ │ │ +M3 = M2#{"function" => fun() -> f() end},
│ │ │ │ +M4 = M3#{a := 2, b := 3}.  % 'a' and 'b' was added in `M1` and `M2`.

Here M0 is any map. It follows that M1 through M4 are maps as well.

More examples:

1> M = #{1 => a}.
│ │ │ │ +#{1 => a }
│ │ │ │ +2> M#{1.0 => b}.
│ │ │ │ +#{1 => a, 1.0 => b}.
│ │ │ │ +3> M#{1 := b}.
│ │ │ │ +#{1 => b}
│ │ │ │ +4> M#{1.0 := b}.
│ │ │ │  ** exception error: bad argument

As in construction, the order in which the key and value expressions are │ │ │ │ evaluated is not defined. The syntactic order of the key-value pairs in the │ │ │ │ update is of no relevance, except in the case where two keys match. In that │ │ │ │ case, the latter value is used.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Maps in Patterns │ │ │ │

│ │ │ │ -

Matching of key-value associations from maps is done as follows:

#{K := V} = M

Here M is any map. The key K must be a │ │ │ │ +

Matching of key-value associations from maps is done as follows:

#{K := V} = M

Here M is any map. The key K must be a │ │ │ │ guard expression, with all variables already │ │ │ │ bound. V can be any pattern with either bound or unbound variables.

If the variable V is unbound, it becomes bound to the value associated with │ │ │ │ the key K, which must exist in the map M. If the variable V is bound, it │ │ │ │ must match the value associated with K in M.

Change

Before Erlang/OTP 23, the expression defining the key K was restricted to be │ │ │ │ -either a single variable or a literal.

Example:

1> M = #{"tuple" => {1,2}}.
│ │ │ │ -#{"tuple" => {1,2}}
│ │ │ │ -2> #{"tuple" := {1,B}} = M.
│ │ │ │ -#{"tuple" => {1,2}}
│ │ │ │ +either a single variable or a literal.

Example:

1> M = #{"tuple" => {1,2}}.
│ │ │ │ +#{"tuple" => {1,2}}
│ │ │ │ +2> #{"tuple" := {1,B}} = M.
│ │ │ │ +#{"tuple" => {1,2}}
│ │ │ │  3> B.
│ │ │ │ -2.

This binds variable B to integer 2.

Similarly, multiple values from the map can be matched:

#{K1 := V1, ..., Kn := Vn} = M

Here keys K1 through Kn are any expressions with literals or bound │ │ │ │ +2.

This binds variable B to integer 2.

Similarly, multiple values from the map can be matched:

#{K1 := V1, ..., Kn := Vn} = M

Here keys K1 through Kn are any expressions with literals or bound │ │ │ │ variables. If all key expressions evaluate successfully and all keys │ │ │ │ exist in map M, all variables in V1 .. Vn is matched to the │ │ │ │ associated values of their respective keys.

If the matching conditions are not met the match fails.

Note that when matching a map, only the := operator (not the =>) is allowed │ │ │ │ as a delimiter for the associations.

The order in which keys are declared in matching has no relevance.

Duplicate keys are allowed in matching and match each pattern associated to the │ │ │ │ -keys:

#{K := V1, K := V2} = M

The empty map literal (#{}) matches any map when used as a pattern:

#{} = Expr

This expression matches if the expression Expr is of type map, otherwise it │ │ │ │ -fails with an exception badmatch.

Here the key to be retrieved is constructed from an expression:

#{{tag,length(List)} := V} = Map

List must be an already bound variable.

Matching Syntax

Matching of literals as keys are allowed in function heads:

%% only start if not_started
│ │ │ │ -handle_call(start, From, #{state := not_started} = S) ->
│ │ │ │ +keys:

#{K := V1, K := V2} = M

The empty map literal (#{}) matches any map when used as a pattern:

#{} = Expr

This expression matches if the expression Expr is of type map, otherwise it │ │ │ │ +fails with an exception badmatch.

Here the key to be retrieved is constructed from an expression:

#{{tag,length(List)} := V} = Map

List must be an already bound variable.

Matching Syntax

Matching of literals as keys are allowed in function heads:

%% only start if not_started
│ │ │ │ +handle_call(start, From, #{state := not_started} = S) ->
│ │ │ │  ...
│ │ │ │ -    {reply, ok, S#{state := start}};
│ │ │ │ +    {reply, ok, S#{state := start}};
│ │ │ │  
│ │ │ │  %% only change if started
│ │ │ │ -handle_call(change, From, #{state := start} = S) ->
│ │ │ │ +handle_call(change, From, #{state := start} = S) ->
│ │ │ │  ...
│ │ │ │ -    {reply, ok, S#{state := changed}};

│ │ │ │ + {reply, ok, S#{state := changed}};

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Maps in Guards │ │ │ │

│ │ │ │

Maps are allowed in guards as long as all subexpressions are valid guard │ │ │ │ expressions.

The following guard BIFs handle maps:

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Bit Syntax Expressions │ │ │ │

│ │ │ │

The bit syntax operates on bit strings. A bit string is a sequence of bits │ │ │ │ -ordered from the most significant bit to the least significant bit.

<<>>  % The empty bit string, zero length
│ │ │ │ -<<E1>>
│ │ │ │ -<<E1,...,En>>

Each element Ei specifies a segment of the bit string. The segments are │ │ │ │ +ordered from the most significant bit to the least significant bit.

<<>>  % The empty bit string, zero length
│ │ │ │ +<<E1>>
│ │ │ │ +<<E1,...,En>>

Each element Ei specifies a segment of the bit string. The segments are │ │ │ │ ordered left to right from the most significant bit to the least significant bit │ │ │ │ of the bit string.

Each segment specification Ei is a value, whose default type is integer, │ │ │ │ followed by an optional size expression and an optional type specifier list.

Ei = Value |
│ │ │ │       Value:Size |
│ │ │ │       Value/TypeSpecifierList |
│ │ │ │       Value:Size/TypeSpecifierList

When used in a bit string construction, Value is an expression that is to │ │ │ │ evaluate to an integer, float, or bit string. If the expression is not a single │ │ │ │ @@ -722,34 +722,34 @@ │ │ │ │ guard expression that evaluates to an │ │ │ │ integer. All variables in the guard expression must be already bound.

Change

Before Erlang/OTP 23, Size was restricted to be an integer or a variable │ │ │ │ bound to an integer.

The value of Size specifies the size of the segment in units (see below). The │ │ │ │ default value depends on the type (see below):

  • For integer it is 8.
  • For float it is 64.
  • For binary and bitstring it is the whole binary or bit string.

In matching, the default value for a binary or bit string segment is only valid │ │ │ │ for the last element. All other bit string or binary elements in the matching │ │ │ │ must have a size specification.

Binaries

A bit string with a length that is a multiple of 8 bits is known as a binary, │ │ │ │ which is the most common and useful type of bit string.

A binary has a canonical representation in memory. Here follows a sequence of │ │ │ │ -bytes where each byte's value is its sequence number:

<<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>>

Bit strings are a later generalization of binaries, so many texts and much │ │ │ │ -information about binaries apply just as well for bit strings.

Example:

1> <<A/binary, B/binary>> = <<"abcde">>.
│ │ │ │ +bytes where each byte's value is its sequence number:

<<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>>

Bit strings are a later generalization of binaries, so many texts and much │ │ │ │ +information about binaries apply just as well for bit strings.

Example:

1> <<A/binary, B/binary>> = <<"abcde">>.
│ │ │ │  * 1:3: a binary field without size is only allowed at the end of a binary pattern
│ │ │ │ -2> <<A:3/binary, B/binary>> = <<"abcde">>.
│ │ │ │ -<<"abcde">>
│ │ │ │ +2> <<A:3/binary, B/binary>> = <<"abcde">>.
│ │ │ │ +<<"abcde">>
│ │ │ │  3> A.
│ │ │ │ -<<"abc">>
│ │ │ │ +<<"abc">>
│ │ │ │  4> B.
│ │ │ │ -<<"de">>

For the utf8, utf16, and utf32 types, Size must not be given. The size │ │ │ │ +<<"de">>

For the utf8, utf16, and utf32 types, Size must not be given. The size │ │ │ │ of the segment is implicitly determined by the type and value itself.

TypeSpecifierList is a list of type specifiers, in any order, separated by │ │ │ │ hyphens (-). Default values are used for any omitted type specifiers.

  • Type= integer | float | binary | bytes | bitstring | bits | │ │ │ │ utf8 | utf16 | utf32 - The default is integer. bytes is a │ │ │ │ shorthand for binary and bits is a shorthand for bitstring. See below │ │ │ │ for more information about the utf types.

  • Signedness= signed | unsigned - Only matters for matching and when │ │ │ │ the type is integer. The default is unsigned.

  • Endianness= big | little | native - Specifies byte level (octet │ │ │ │ level) endianness (byte order). Native-endian means that the endianness is │ │ │ │ resolved at load time to be either big-endian or little-endian, depending on │ │ │ │ what is native for the CPU that the Erlang machine is run on. Endianness only │ │ │ │ matters when the Type is either integer, utf16, utf32, or float. The │ │ │ │ -default is big.

    <<16#1234:16/little>> = <<16#3412:16>> = <<16#34:8, 16#12:8>>
  • Unit= unit:IntegerLiteral - The allowed range is 1 through 256. │ │ │ │ +default is big.

    <<16#1234:16/little>> = <<16#3412:16>> = <<16#34:8, 16#12:8>>
  • Unit= unit:IntegerLiteral - The allowed range is 1 through 256. │ │ │ │ Defaults to 1 for integer, float, and bitstring, and to 8 for binary. │ │ │ │ For types bitstring, bits, and bytes, it is not allowed to specify a │ │ │ │ unit value different from the default value. No unit specifier must be given │ │ │ │ for the types utf8, utf16, and utf32.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -774,41 +774,41 @@ │ │ │ │ │ │ │ │ Binary segments │ │ │ │

│ │ │ │

In this section, the phrase "binary segment" refers to any one of the segment │ │ │ │ types binary, bitstring, bytes, and bits.

See also the paragraphs about Binaries.

When constructing binaries and no size is specified for a binary segment, the │ │ │ │ entire binary value is interpolated into the binary being constructed. However, │ │ │ │ the size in bits of the binary being interpolated must be evenly divisible by │ │ │ │ -the unit value for the segment; otherwise an exception is raised.

For example, the following examples all succeed:

1> <<(<<"abc">>)/bitstring>>.
│ │ │ │ -<<"abc">>
│ │ │ │ -2> <<(<<"abc">>)/binary-unit:1>>.
│ │ │ │ -<<"abc">>
│ │ │ │ -3> <<(<<"abc">>)/binary>>.
│ │ │ │ -<<"abc">>

The first two examples have a unit value of 1 for the segment, while the third │ │ │ │ +the unit value for the segment; otherwise an exception is raised.

For example, the following examples all succeed:

1> <<(<<"abc">>)/bitstring>>.
│ │ │ │ +<<"abc">>
│ │ │ │ +2> <<(<<"abc">>)/binary-unit:1>>.
│ │ │ │ +<<"abc">>
│ │ │ │ +3> <<(<<"abc">>)/binary>>.
│ │ │ │ +<<"abc">>

The first two examples have a unit value of 1 for the segment, while the third │ │ │ │ segment has a unit value of 8.

Attempting to interpolate a bit string of size 1 into a binary segment with unit │ │ │ │ -8 (the default unit for binary) fails as shown in this example:

1> <<(<<1:1>>)/binary>>.
│ │ │ │ -** exception error: bad argument

For the construction to succeed, the unit value of the segment must be 1:

2> <<(<<1:1>>)/bitstring>>.
│ │ │ │ -<<1:1>>
│ │ │ │ -3> <<(<<1:1>>)/binary-unit:1>>.
│ │ │ │ -<<1:1>>

Similarly, when matching a binary segment with no size specified, the match │ │ │ │ +8 (the default unit for binary) fails as shown in this example:

1> <<(<<1:1>>)/binary>>.
│ │ │ │ +** exception error: bad argument

For the construction to succeed, the unit value of the segment must be 1:

2> <<(<<1:1>>)/bitstring>>.
│ │ │ │ +<<1:1>>
│ │ │ │ +3> <<(<<1:1>>)/binary-unit:1>>.
│ │ │ │ +<<1:1>>

Similarly, when matching a binary segment with no size specified, the match │ │ │ │ succeeds if and only if the size in bits of the rest of the binary is evenly │ │ │ │ -divisible by the unit value:

1> <<_/binary-unit:16>> = <<"">>.
│ │ │ │ -<<>>
│ │ │ │ -2> <<_/binary-unit:16>> = <<"a">>.
│ │ │ │ +divisible by the unit value:

1> <<_/binary-unit:16>> = <<"">>.
│ │ │ │ +<<>>
│ │ │ │ +2> <<_/binary-unit:16>> = <<"a">>.
│ │ │ │  ** exception error: no match of right hand side value <<"a">>
│ │ │ │ -3> <<_/binary-unit:16>> = <<"ab">>.
│ │ │ │ -<<"ab">>
│ │ │ │ -4> <<_/binary-unit:16>> = <<"abc">>.
│ │ │ │ +3> <<_/binary-unit:16>> = <<"ab">>.
│ │ │ │ +<<"ab">>
│ │ │ │ +4> <<_/binary-unit:16>> = <<"abc">>.
│ │ │ │  ** exception error: no match of right hand side value <<"abc">>
│ │ │ │ -5> <<_/binary-unit:16>> = <<"abcd">>.
│ │ │ │ -<<"abcd">>

When a size is explicitly specified for a binary segment, the segment size in │ │ │ │ +5> <<_/binary-unit:16>> = <<"abcd">>. │ │ │ │ +<<"abcd">>

When a size is explicitly specified for a binary segment, the segment size in │ │ │ │ bits is the value of Size multiplied by the default or explicit unit value.

When constructing binaries, the size of the binary being interpolated into the │ │ │ │ -constructed binary must be at least as large as the size of the binary segment.

Examples:

1> <<(<<"abc">>):2/binary>>.
│ │ │ │ -<<"ab">>
│ │ │ │ -2> <<(<<"a">>):2/binary>>.
│ │ │ │ +constructed binary must be at least as large as the size of the binary segment.

Examples:

1> <<(<<"abc">>):2/binary>>.
│ │ │ │ +<<"ab">>
│ │ │ │ +2> <<(<<"a">>):2/binary>>.
│ │ │ │  ** exception error: construction of binary failed
│ │ │ │          *** segment 1 of type 'binary': the value <<"a">> is shorter than the size of the segment

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Unicode segments │ │ │ │

│ │ │ │ @@ -824,78 +824,78 @@ │ │ │ │ range 0 through 16#D7FF or 16#E000 through 16#10FFFF. The match fails if the │ │ │ │ returned value falls outside those ranges.

A segment of type utf8 matches 1-4 bytes in the bit string, if the bit string │ │ │ │ at the match position contains a valid UTF-8 sequence. (See RFC-3629 or the │ │ │ │ Unicode standard.)

A segment of type utf16 can match 2 or 4 bytes in the bit string. The match │ │ │ │ fails if the bit string at the match position does not contain a legal UTF-16 │ │ │ │ encoding of a Unicode code point. (See RFC-2781 or the Unicode standard.)

A segment of type utf32 can match 4 bytes in the bit string in the same way as │ │ │ │ an integer segment matches 32 bits. The match fails if the resulting integer │ │ │ │ -is outside the legal ranges previously mentioned.

Examples:

1> Bin1 = <<1,17,42>>.
│ │ │ │ -<<1,17,42>>
│ │ │ │ -2> Bin2 = <<"abc">>.
│ │ │ │ -<<97,98,99>>
│ │ │ │ +is outside the legal ranges previously mentioned.

Examples:

1> Bin1 = <<1,17,42>>.
│ │ │ │ +<<1,17,42>>
│ │ │ │ +2> Bin2 = <<"abc">>.
│ │ │ │ +<<97,98,99>>
│ │ │ │  
│ │ │ │ -3> Bin3 = <<1,17,42:16>>.
│ │ │ │ -<<1,17,0,42>>
│ │ │ │ -4> <<A,B,C:16>> = <<1,17,42:16>>.
│ │ │ │ -<<1,17,0,42>>
│ │ │ │ +3> Bin3 = <<1,17,42:16>>.
│ │ │ │ +<<1,17,0,42>>
│ │ │ │ +4> <<A,B,C:16>> = <<1,17,42:16>>.
│ │ │ │ +<<1,17,0,42>>
│ │ │ │  5> C.
│ │ │ │  42
│ │ │ │ -6> <<D:16,E,F>> = <<1,17,42:16>>.
│ │ │ │ -<<1,17,0,42>>
│ │ │ │ +6> <<D:16,E,F>> = <<1,17,42:16>>.
│ │ │ │ +<<1,17,0,42>>
│ │ │ │  7> D.
│ │ │ │  273
│ │ │ │  8> F.
│ │ │ │  42
│ │ │ │ -9> <<G,H/binary>> = <<1,17,42:16>>.
│ │ │ │ -<<1,17,0,42>>
│ │ │ │ +9> <<G,H/binary>> = <<1,17,42:16>>.
│ │ │ │ +<<1,17,0,42>>
│ │ │ │  10> H.
│ │ │ │ -<<17,0,42>>
│ │ │ │ -11> <<G,J/bitstring>> = <<1,17,42:12>>.
│ │ │ │ -<<1,17,2,10:4>>
│ │ │ │ +<<17,0,42>>
│ │ │ │ +11> <<G,J/bitstring>> = <<1,17,42:12>>.
│ │ │ │ +<<1,17,2,10:4>>
│ │ │ │  12> J.
│ │ │ │ -<<17,2,10:4>>
│ │ │ │ +<<17,2,10:4>>
│ │ │ │  
│ │ │ │ -13> <<1024/utf8>>.
│ │ │ │ -<<208,128>>
│ │ │ │ +13> <<1024/utf8>>.
│ │ │ │ +<<208,128>>
│ │ │ │  
│ │ │ │ -14> <<1:1,0:7>>.
│ │ │ │ -<<128>>
│ │ │ │ -15> <<16#123:12/little>> = <<16#231:12>> = <<2:4, 3:4, 1:4>>.
│ │ │ │ -<<35,1:4>>

Notice that bit string patterns cannot be nested.

Notice also that "B=<<1>>" is interpreted as "B =< <1>>" which is a syntax │ │ │ │ +14> <<1:1,0:7>>. │ │ │ │ +<<128>> │ │ │ │ +15> <<16#123:12/little>> = <<16#231:12>> = <<2:4, 3:4, 1:4>>. │ │ │ │ +<<35,1:4>>

Notice that bit string patterns cannot be nested.

Notice also that "B=<<1>>" is interpreted as "B =< <1>>" which is a syntax │ │ │ │ error. The correct way is to write a space after =: "B = <<1>>.

More examples are provided in Programming Examples.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Fun Expressions │ │ │ │

│ │ │ │
fun
│ │ │ │ -    [Name](Pattern11,...,Pattern1N) [when GuardSeq1] ->
│ │ │ │ +    [Name](Pattern11,...,Pattern1N) [when GuardSeq1] ->
│ │ │ │                Body1;
│ │ │ │      ...;
│ │ │ │ -    [Name](PatternK1,...,PatternKN) [when GuardSeqK] ->
│ │ │ │ +    [Name](PatternK1,...,PatternKN) [when GuardSeqK] ->
│ │ │ │                BodyK
│ │ │ │  end

A fun expression begins with the keyword fun and ends with the keyword end. │ │ │ │ Between them is to be a function declaration, similar to a │ │ │ │ regular function declaration, │ │ │ │ except that the function name is optional and is to be a variable, if any.

Variables in a fun head shadow the function name and both shadow variables in │ │ │ │ the function clause surrounding the fun expression. Variables bound in a fun │ │ │ │ -body are local to the fun body.

The return value of the expression is the resulting fun.

Examples:

1> Fun1 = fun (X) -> X+1 end.
│ │ │ │ +body are local to the fun body.

The return value of the expression is the resulting fun.

Examples:

1> Fun1 = fun (X) -> X+1 end.
│ │ │ │  #Fun<erl_eval.6.39074546>
│ │ │ │ -2> Fun1(2).
│ │ │ │ +2> Fun1(2).
│ │ │ │  3
│ │ │ │ -3> Fun2 = fun (X) when X>=5 -> gt; (X) -> lt end.
│ │ │ │ +3> Fun2 = fun (X) when X>=5 -> gt; (X) -> lt end.
│ │ │ │  #Fun<erl_eval.6.39074546>
│ │ │ │ -4> Fun2(7).
│ │ │ │ +4> Fun2(7).
│ │ │ │  gt
│ │ │ │ -5> Fun3 = fun Fact(1) -> 1; Fact(X) when X > 1 -> X * Fact(X - 1) end.
│ │ │ │ +5> Fun3 = fun Fact(1) -> 1; Fact(X) when X > 1 -> X * Fact(X - 1) end.
│ │ │ │  #Fun<erl_eval.6.39074546>
│ │ │ │ -6> Fun3(4).
│ │ │ │ +6> Fun3(4).
│ │ │ │  24

The following fun expressions are also allowed:

fun Name/Arity
│ │ │ │  fun Module:Name/Arity

In Name/Arity, Name is an atom and Arity is an integer. Name/Arity must │ │ │ │ -specify an existing local function. The expression is syntactic sugar for:

fun (Arg1,...,ArgN) -> Name(Arg1,...,ArgN) end

In Module:Name/Arity, Module, and Name are atoms and Arity is an │ │ │ │ +specify an existing local function. The expression is syntactic sugar for:

fun (Arg1,...,ArgN) -> Name(Arg1,...,ArgN) end

In Module:Name/Arity, Module, and Name are atoms and Arity is an │ │ │ │ integer. Module, Name, and Arity can also be variables. A fun defined in │ │ │ │ this way refers to the function Name with arity Arity in the latest │ │ │ │ version of module Module. A fun defined in this way is not dependent on the │ │ │ │ code for the module in which it is defined.

Change

Before Erlang/OTP R15, Module, Name, and Arity were not allowed to be │ │ │ │ variables.

More examples are provided in Programming Examples.

│ │ │ │ │ │ │ │ │ │ │ │ @@ -905,35 +905,35 @@ │ │ │ │
catch Expr

Returns the value of Expr unless an exception is raised during the evaluation. In │ │ │ │ that case, the exception is caught. The return value depends on the class of the │ │ │ │ exception:

Reason depends on the type of error that occurred, and Stack is the stack of │ │ │ │ recent function calls, see Exit Reasons.

Examples:

1> catch 1+2.
│ │ │ │  3
│ │ │ │  2> catch 1+a.
│ │ │ │ -{'EXIT',{badarith,[...]}}

The BIF throw(Any) can be used for non-local return from a │ │ │ │ -function. It must be evaluated within a catch, which returns the value Any.

Example:

3> catch throw(hello).
│ │ │ │ +{'EXIT',{badarith,[...]}}

The BIF throw(Any) can be used for non-local return from a │ │ │ │ +function. It must be evaluated within a catch, which returns the value Any.

Example:

3> catch throw(hello).
│ │ │ │  hello

If throw/1 is not evaluated within a catch, a nocatch run-time │ │ │ │ error occurs.

Change

Before Erlang/OTP 24, the catch operator had the lowest precedence, making │ │ │ │ -it necessary to add parentheses when combining it with the match operator:

1> A = (catch 42).
│ │ │ │ +it necessary to add parentheses when combining it with the match operator:

1> A = (catch 42).
│ │ │ │  42
│ │ │ │  2> A.
│ │ │ │  42

Starting from Erlang/OTP 24, the parentheses can be omitted:

1> A = catch 42.
│ │ │ │  42
│ │ │ │  2> A.
│ │ │ │  42

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Try │ │ │ │

│ │ │ │
try Exprs
│ │ │ │  catch
│ │ │ │ -    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
│ │ │ │ +    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
│ │ │ │          ExceptionBody1;
│ │ │ │ -    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
│ │ │ │ +    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
│ │ │ │          ExceptionBodyN
│ │ │ │  end

This is an enhancement of catch. It gives the │ │ │ │ possibility to:

  • Distinguish between different exception classes.
  • Choose to handle only the desired ones.
  • Passing the others on to an enclosing try or catch, or to default error │ │ │ │ handling.

Notice that although the keyword catch is used in the try expression, there │ │ │ │ is not a catch expression within the try expression.

It returns the value of Exprs (a sequence of expressions Expr1, ..., ExprN) │ │ │ │ unless an exception occurs during the evaluation. In that case the exception is │ │ │ │ caught and the patterns ExceptionPattern with the right exception class │ │ │ │ @@ -943,47 +943,47 @@ │ │ │ │ stack trace is bound to the variable when the corresponding ExceptionPattern │ │ │ │ matches.

If an exception occurs during evaluation of Exprs but there is no matching │ │ │ │ ExceptionPattern of the right Class with a true guard sequence, the │ │ │ │ exception is passed on as if Exprs had not been enclosed in a try │ │ │ │ expression.

If an exception occurs during evaluation of ExceptionBody, it is not caught.

It is allowed to omit Class and Stacktrace. An omitted Class is shorthand │ │ │ │ for throw:

try Exprs
│ │ │ │  catch
│ │ │ │ -    ExceptionPattern1 [when ExceptionGuardSeq1] ->
│ │ │ │ +    ExceptionPattern1 [when ExceptionGuardSeq1] ->
│ │ │ │          ExceptionBody1;
│ │ │ │ -    ExceptionPatternN [when ExceptionGuardSeqN] ->
│ │ │ │ +    ExceptionPatternN [when ExceptionGuardSeqN] ->
│ │ │ │          ExceptionBodyN
│ │ │ │  end

The try expression can have an of section:

try Exprs of
│ │ │ │ -    Pattern1 [when GuardSeq1] ->
│ │ │ │ +    Pattern1 [when GuardSeq1] ->
│ │ │ │          Body1;
│ │ │ │      ...;
│ │ │ │ -    PatternN [when GuardSeqN] ->
│ │ │ │ +    PatternN [when GuardSeqN] ->
│ │ │ │          BodyN
│ │ │ │  catch
│ │ │ │ -    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
│ │ │ │ +    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
│ │ │ │          ExceptionBody1;
│ │ │ │      ...;
│ │ │ │ -    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
│ │ │ │ +    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
│ │ │ │          ExceptionBodyN
│ │ │ │  end

If the evaluation of Exprs succeeds without an exception, the patterns │ │ │ │ Pattern are sequentially matched against the result in the same way as for a │ │ │ │ case expression, except that if the matching fails, a │ │ │ │ try_clause run-time error occurs instead of a case_clause.

Only exceptions occurring during the evaluation of Exprs can be caught by the │ │ │ │ catch section. Exceptions occurring in a Body or due to a failed match are │ │ │ │ not caught.

The try expression can also be augmented with an after section, intended to │ │ │ │ be used for cleanup with side effects:

try Exprs of
│ │ │ │ -    Pattern1 [when GuardSeq1] ->
│ │ │ │ +    Pattern1 [when GuardSeq1] ->
│ │ │ │          Body1;
│ │ │ │      ...;
│ │ │ │ -    PatternN [when GuardSeqN] ->
│ │ │ │ +    PatternN [when GuardSeqN] ->
│ │ │ │          BodyN
│ │ │ │  catch
│ │ │ │ -    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
│ │ │ │ +    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
│ │ │ │          ExceptionBody1;
│ │ │ │      ...;
│ │ │ │ -    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
│ │ │ │ +    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
│ │ │ │          ExceptionBodyN
│ │ │ │  after
│ │ │ │      AfterBody
│ │ │ │  end

AfterBody is evaluated after either Body or ExceptionBody, no matter which │ │ │ │ one. The evaluated value of AfterBody is lost; the return value of the try │ │ │ │ expression is the same with an after section as without.

Even if an exception occurs during evaluation of Body or ExceptionBody, │ │ │ │ AfterBody is evaluated. In this case the exception is passed on after │ │ │ │ @@ -1006,40 +1006,40 @@ │ │ │ │ ExpressionBody │ │ │ │ after │ │ │ │ AfterBody │ │ │ │ end │ │ │ │ │ │ │ │ try Exprs after AfterBody end

Next is an example of using after. This closes the file, even in the event of │ │ │ │ exceptions in file:read/2 or in binary_to_term/1. The │ │ │ │ -exceptions are the same as without the try...after...end expression:

termize_file(Name) ->
│ │ │ │ -    {ok,F} = file:open(Name, [read,binary]),
│ │ │ │ +exceptions are the same as without the try...after...end expression:

termize_file(Name) ->
│ │ │ │ +    {ok,F} = file:open(Name, [read,binary]),
│ │ │ │      try
│ │ │ │ -        {ok,Bin} = file:read(F, 1024*1024),
│ │ │ │ -        binary_to_term(Bin)
│ │ │ │ +        {ok,Bin} = file:read(F, 1024*1024),
│ │ │ │ +        binary_to_term(Bin)
│ │ │ │      after
│ │ │ │ -        file:close(F)
│ │ │ │ +        file:close(F)
│ │ │ │      end.

Next is an example of using try to emulate catch Expr:

try Expr
│ │ │ │  catch
│ │ │ │      throw:Term -> Term;
│ │ │ │ -    exit:Reason -> {'EXIT',Reason};
│ │ │ │ -    error:Reason:Stk -> {'EXIT',{Reason,Stk}}
│ │ │ │ +    exit:Reason -> {'EXIT',Reason};
│ │ │ │ +    error:Reason:Stk -> {'EXIT',{Reason,Stk}}
│ │ │ │  end

Variables bound in the various parts of these expressions have different scopes. │ │ │ │ Variables bound just after the try keyword are:

  • bound in the of section
  • unsafe in both the catch and after sections, as well as after the whole │ │ │ │ construct

Variables bound in of section are:

  • unbound in the catch section
  • unsafe in both the after section, as well as after the whole construct

Variables bound in the catch section are unsafe in the after section, as │ │ │ │ well as after the whole construct.

Variables bound in the after section are unsafe after the whole construct.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Parenthesized Expressions │ │ │ │

│ │ │ │ -
(Expr)

Parenthesized expressions are useful to override │ │ │ │ +

(Expr)

Parenthesized expressions are useful to override │ │ │ │ operator precedences, for example, in arithmetic │ │ │ │ expressions:

1> 1 + 2 * 3.
│ │ │ │  7
│ │ │ │ -2> (1 + 2) * 3.
│ │ │ │ +2> (1 + 2) * 3.
│ │ │ │  9

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Block Expressions │ │ │ │

│ │ │ │
begin
│ │ │ │ @@ -1051,82 +1051,82 @@
│ │ │ │    
│ │ │ │      
│ │ │ │    
│ │ │ │    Comprehensions
│ │ │ │  

│ │ │ │

Comprehensions provide a succinct notation for iterating over one or more terms │ │ │ │ and constructing a new term. Comprehensions come in three different flavors, │ │ │ │ -depending on the type of term they build.

List comprehensions construct lists. They have the following syntax:

[Expr || Qualifier1, . . ., QualifierN]

Here, Expr is an arbitrary expression, and each Qualifier is either a │ │ │ │ +depending on the type of term they build.

List comprehensions construct lists. They have the following syntax:

[Expr || Qualifier1, . . ., QualifierN]

Here, Expr is an arbitrary expression, and each Qualifier is either a │ │ │ │ generator or a filter.

Bit string comprehensions construct bit strings or binaries. They have the │ │ │ │ -following syntax:

<< BitStringExpr || Qualifier1, . . ., QualifierN >>

BitStringExpr is an expression that evaluates to a bit string. If │ │ │ │ +following syntax:

<< BitStringExpr || Qualifier1, . . ., QualifierN >>

BitStringExpr is an expression that evaluates to a bit string. If │ │ │ │ BitStringExpr is a function call, it must be enclosed in parentheses. Each │ │ │ │ -Qualifier is either a generator or a filter.

Map comprehensions construct maps. They have the following syntax:

#{KeyExpr => ValueExpr || Qualifier1, . . ., QualifierN}

Here, KeyExpr and ValueExpr are arbitrary expressions, and each Qualifier │ │ │ │ +Qualifier is either a generator or a filter.

Map comprehensions construct maps. They have the following syntax:

#{KeyExpr => ValueExpr || Qualifier1, . . ., QualifierN}

Here, KeyExpr and ValueExpr are arbitrary expressions, and each Qualifier │ │ │ │ is either a generator or a filter.

Change

Map comprehensions and map generators were introduced in Erlang/OTP 26.

There are four kinds of generators. Three of them have a relaxed and a strict │ │ │ │ variant. The fourth kind of generator, zip generator, is composed by two or │ │ │ │ more non-zip generators.

Change

Strict generators and zip generators were introduced in Erlang/OTP 28. │ │ │ │ Using strict generators is a better practice when either strict or relaxed │ │ │ │ generators work. More details are in │ │ │ │ Programming Examples.

A list generator has the following syntax for relaxed:

Pattern <- ListExpr

and strict variant:

Pattern <:- ListExpr

where ListExpr is an expression that evaluates to a list of terms.

A bit string generator has the following syntax for relaxed:

BitstringPattern <= BitStringExpr

and strict variant:

BitstringPattern <:= BitStringExpr

where BitStringExpr is an expression that evaluates to a bit string.

A map generator has the following syntax for relaxed:

KeyPattern := ValuePattern <- MapExpression

and strict variant:

KeyPattern := ValuePattern <:- MapExpression

where MapExpr is an expression that evaluates to a map, or a map iterator │ │ │ │ obtained by calling maps:iterator/1 or maps:iterator/2.

A zip generator has the following syntax:

Generator_1 && ... && Generator_n

where every Generator_i is a non-zip generator. Generators within a zip │ │ │ │ generator are treated as one generator and evaluated in parallel.

A filter is an expression that evaluates to true or false.

The variables in the generator patterns shadow previously bound variables, │ │ │ │ including variables bound in a previous generator pattern.

Variables bound in a generator expression are not visible outside the │ │ │ │ -expression:

1> [{E,L} || E <- L=[1,2,3]].
│ │ │ │ +expression:

1> [{E,L} || E <- L=[1,2,3]].
│ │ │ │  * 1:5: variable 'L' is unbound

A list comprehension returns a list, where the list elements are the result │ │ │ │ of evaluating Expr for each combination of generator elements for which all │ │ │ │ filters are true.

A bit string comprehension returns a bit string, which is created by │ │ │ │ concatenating the results of evaluating BitStringExpr for each combination of │ │ │ │ bit string generator elements for which all filters are true.

A map comprehension returns a map, where the map elements are the result of │ │ │ │ evaluating KeyExpr and ValueExpr for each combination of generator elements │ │ │ │ for which all filters are true. If the key expressions are not unique, the last │ │ │ │ -occurrence is stored in the map.

Examples:

Multiplying each element in a list by two:

1> [X*2 || X <:- [1,2,3]].
│ │ │ │ -[2,4,6]

Multiplying each byte in a binary by two, returning a list:

1> [X*2 || <<X>> <:= <<1,2,3>>].
│ │ │ │ -[2,4,6]

Multiplying each byte in a binary by two:

1> << <<(X*2)>> || <<X>> <:= <<1,2,3>> >>.
│ │ │ │ -<<2,4,6>>

Multiplying each element in a list by two, returning a binary:

1> << <<(X*2)>> || X <:- [1,2,3] >>.
│ │ │ │ -<<2,4,6>>

Creating a mapping from an integer to its square:

1> #{X => X*X || X <:- [1,2,3]}.
│ │ │ │ -#{1 => 1,2 => 4,3 => 9}

Multiplying the value of each element in a map by two:

1> #{K => 2*V || K := V <:- #{a => 1,b => 2,c => 3}}.
│ │ │ │ -#{a => 2,b => 4,c => 6}

Filtering a list, keeping odd numbers:

1> [X || X <:- [1,2,3,4,5], X rem 2 =:= 1].
│ │ │ │ -[1,3,5]

Filtering a list, keeping only elements that match:

1> [X || {_,_}=X <- [{a,b}, [a], {x,y,z}, {1,2}]].
│ │ │ │ -[{a,b},{1,2}]

Filtering a list, crashing when the element is not a 2-tuple:

1> [X || {_,_}=X <:- [{a,b}, [a], {x,y,z}, {1,2}]].
│ │ │ │ -** exception error: no match of right hand side value [a]

Combining elements from two list generators:

1> [{P,Q} || P <:- [a,b,c], Q <:- [1,2]].
│ │ │ │ -[{a,1},{a,2},{b,1},{b,2},{c,1},{c,2}]

Combining elements from two list generators, using a zip generator:

1> [{P,Q} || P <:- [a,b,c] && Q <:- [1,2,3]].
│ │ │ │ -[{a,1},{b,2},{c,3}]

Combining elements from two list generators using a zip generator, filtering │ │ │ │ -out odd numbers:

1> [{P,Q} || P <:- [a,b,c] && Q <:- [1,2,3], Q rem 2 =:= 0].
│ │ │ │ -[{a,1},{b,2},{c,3}]

Filtering out non-matching elements from two lists.

1> [X || X <- [1,2,3,5] && X <- [1,4,3,6]].
│ │ │ │ -[1,3]

More examples are provided in │ │ │ │ +occurrence is stored in the map.

Examples:

Multiplying each element in a list by two:

1> [X*2 || X <:- [1,2,3]].
│ │ │ │ +[2,4,6]

Multiplying each byte in a binary by two, returning a list:

1> [X*2 || <<X>> <:= <<1,2,3>>].
│ │ │ │ +[2,4,6]

Multiplying each byte in a binary by two:

1> << <<(X*2)>> || <<X>> <:= <<1,2,3>> >>.
│ │ │ │ +<<2,4,6>>

Multiplying each element in a list by two, returning a binary:

1> << <<(X*2)>> || X <:- [1,2,3] >>.
│ │ │ │ +<<2,4,6>>

Creating a mapping from an integer to its square:

1> #{X => X*X || X <:- [1,2,3]}.
│ │ │ │ +#{1 => 1,2 => 4,3 => 9}

Multiplying the value of each element in a map by two:

1> #{K => 2*V || K := V <:- #{a => 1,b => 2,c => 3}}.
│ │ │ │ +#{a => 2,b => 4,c => 6}

Filtering a list, keeping odd numbers:

1> [X || X <:- [1,2,3,4,5], X rem 2 =:= 1].
│ │ │ │ +[1,3,5]

Filtering a list, keeping only elements that match:

1> [X || {_,_}=X <- [{a,b}, [a], {x,y,z}, {1,2}]].
│ │ │ │ +[{a,b},{1,2}]

Filtering a list, crashing when the element is not a 2-tuple:

1> [X || {_,_}=X <:- [{a,b}, [a], {x,y,z}, {1,2}]].
│ │ │ │ +** exception error: no match of right hand side value [a]

Combining elements from two list generators:

1> [{P,Q} || P <:- [a,b,c], Q <:- [1,2]].
│ │ │ │ +[{a,1},{a,2},{b,1},{b,2},{c,1},{c,2}]

Combining elements from two list generators, using a zip generator:

1> [{P,Q} || P <:- [a,b,c] && Q <:- [1,2,3]].
│ │ │ │ +[{a,1},{b,2},{c,3}]

Combining elements from two list generators using a zip generator, filtering │ │ │ │ +out odd numbers:

1> [{P,Q} || P <:- [a,b,c] && Q <:- [1,2,3], Q rem 2 =:= 0].
│ │ │ │ +[{a,1},{b,2},{c,3}]

Filtering out non-matching elements from two lists.

1> [X || X <- [1,2,3,5] && X <- [1,4,3,6]].
│ │ │ │ +[1,3]

More examples are provided in │ │ │ │ Programming Examples.

When there are no generators, a comprehension returns either a term constructed │ │ │ │ from a single element (the result of evaluating Expr) if all filters are true, │ │ │ │ or a term constructed from no elements (that is, [] for list comprehension, │ │ │ │ -<<>> for a bit string comprehension, and #{} for a map comprehension).

Example:

1> [2 || is_integer(2)].
│ │ │ │ -[2]
│ │ │ │ -2> [x || is_integer(x)].
│ │ │ │ -[]

What happens when the filter expression does not evaluate to a boolean value │ │ │ │ +<<>> for a bit string comprehension, and #{} for a map comprehension).

Example:

1> [2 || is_integer(2)].
│ │ │ │ +[2]
│ │ │ │ +2> [x || is_integer(x)].
│ │ │ │ +[]

What happens when the filter expression does not evaluate to a boolean value │ │ │ │ depends on the expression:

  • If the expression is a guard expression, │ │ │ │ failure to evaluate or evaluating to a non-boolean value is equivalent to │ │ │ │ evaluating to false.
  • If the expression is not a guard expression and evaluates to a non-Boolean │ │ │ │ value Val, an exception {bad_filter, Val} is triggered at runtime. If the │ │ │ │ evaluation of the expression raises an exception, it is not caught by the │ │ │ │ -comprehension.

Examples (using a guard expression as filter):

1> List = [1,2,a,b,c,3,4].
│ │ │ │ -[1,2,a,b,c,3,4]
│ │ │ │ -2> [E || E <:- List, E rem 2].
│ │ │ │ -[]
│ │ │ │ -3> [E || E <:- List, E rem 2 =:= 0].
│ │ │ │ -[2,4]

Examples (using a non-guard expression as filter):

1> List = [1,2,a,b,c,3,4].
│ │ │ │ -[1,2,a,b,c,3,4]
│ │ │ │ -2> FaultyIsEven = fun(E) -> E rem 2 end.
│ │ │ │ +comprehension.

Examples (using a guard expression as filter):

1> List = [1,2,a,b,c,3,4].
│ │ │ │ +[1,2,a,b,c,3,4]
│ │ │ │ +2> [E || E <:- List, E rem 2].
│ │ │ │ +[]
│ │ │ │ +3> [E || E <:- List, E rem 2 =:= 0].
│ │ │ │ +[2,4]

Examples (using a non-guard expression as filter):

1> List = [1,2,a,b,c,3,4].
│ │ │ │ +[1,2,a,b,c,3,4]
│ │ │ │ +2> FaultyIsEven = fun(E) -> E rem 2 end.
│ │ │ │  #Fun<erl_eval.42.17316486>
│ │ │ │ -3> [E || E <:- List, FaultyIsEven(E)].
│ │ │ │ +3> [E || E <:- List, FaultyIsEven(E)].
│ │ │ │  ** exception error: bad filter 1
│ │ │ │ -4> IsEven = fun(E) -> E rem 2 =:= 0 end.
│ │ │ │ +4> IsEven = fun(E) -> E rem 2 =:= 0 end.
│ │ │ │  #Fun<erl_eval.42.17316486>
│ │ │ │ -5> [E || E <:- List, IsEven(E)].
│ │ │ │ +5> [E || E <:- List, IsEven(E)].
│ │ │ │  ** exception error: an error occurred when evaluating an arithmetic expression
│ │ │ │       in operator  rem/2
│ │ │ │          called as a rem 2
│ │ │ │ -6> [E || E <:- List, is_integer(E), IsEven(E)].
│ │ │ │ -[2,4]

│ │ │ │ +6> [E || E <:- List, is_integer(E), IsEven(E)]. │ │ │ │ +[2,4]

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Guard Sequences │ │ │ │

│ │ │ │

A guard sequence is a sequence of guards, separated by semicolon (;). The │ │ │ │ guard sequence is true if at least one of the guards is true. (The remaining │ │ │ ├── OEBPS/example.xhtml │ │ │ │ @@ -36,14 +36,14 @@ │ │ │ │ │ │ │ │ int bar(int y) { │ │ │ │ return y*2; │ │ │ │ }

The functions are deliberately kept as simple as possible, for readability │ │ │ │ reasons.

From an Erlang perspective, it is preferable to be able to call foo and bar │ │ │ │ without having to bother about that they are C functions:

% Erlang code
│ │ │ │  ...
│ │ │ │ -Res = complex:foo(X),
│ │ │ │ +Res = complex:foo(X),
│ │ │ │  ...

Here, the communication with C is hidden in the implementation of complex.erl. │ │ │ │ In the following sections, it is shown how this module can be implemented using │ │ │ │ the different interoperability mechanisms.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/events.xhtml │ │ │ │ @@ -40,43 +40,43 @@ │ │ │ │ event handler.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Example │ │ │ │

│ │ │ │

The callback module for the event handler writing error messages to the terminal │ │ │ │ -can look as follows:

-module(terminal_logger).
│ │ │ │ --behaviour(gen_event).
│ │ │ │ +can look as follows:

-module(terminal_logger).
│ │ │ │ +-behaviour(gen_event).
│ │ │ │  
│ │ │ │ --export([init/1, handle_event/2, terminate/2]).
│ │ │ │ +-export([init/1, handle_event/2, terminate/2]).
│ │ │ │  
│ │ │ │ -init(_Args) ->
│ │ │ │ -    {ok, []}.
│ │ │ │ +init(_Args) ->
│ │ │ │ +    {ok, []}.
│ │ │ │  
│ │ │ │ -handle_event(ErrorMsg, State) ->
│ │ │ │ -    io:format("***Error*** ~p~n", [ErrorMsg]),
│ │ │ │ -    {ok, State}.
│ │ │ │ +handle_event(ErrorMsg, State) ->
│ │ │ │ +    io:format("***Error*** ~p~n", [ErrorMsg]),
│ │ │ │ +    {ok, State}.
│ │ │ │  
│ │ │ │ -terminate(_Args, _State) ->
│ │ │ │ +terminate(_Args, _State) ->
│ │ │ │      ok.

The callback module for the event handler writing error messages to a file can │ │ │ │ -look as follows:

-module(file_logger).
│ │ │ │ --behaviour(gen_event).
│ │ │ │ +look as follows:

-module(file_logger).
│ │ │ │ +-behaviour(gen_event).
│ │ │ │  
│ │ │ │ --export([init/1, handle_event/2, terminate/2]).
│ │ │ │ +-export([init/1, handle_event/2, terminate/2]).
│ │ │ │  
│ │ │ │ -init(File) ->
│ │ │ │ -    {ok, Fd} = file:open(File, read),
│ │ │ │ -    {ok, Fd}.
│ │ │ │ -
│ │ │ │ -handle_event(ErrorMsg, Fd) ->
│ │ │ │ -    io:format(Fd, "***Error*** ~p~n", [ErrorMsg]),
│ │ │ │ -    {ok, Fd}.
│ │ │ │ +init(File) ->
│ │ │ │ +    {ok, Fd} = file:open(File, read),
│ │ │ │ +    {ok, Fd}.
│ │ │ │ +
│ │ │ │ +handle_event(ErrorMsg, Fd) ->
│ │ │ │ +    io:format(Fd, "***Error*** ~p~n", [ErrorMsg]),
│ │ │ │ +    {ok, Fd}.
│ │ │ │  
│ │ │ │ -terminate(_Args, Fd) ->
│ │ │ │ -    file:close(Fd).

The code is explained in the next sections.

│ │ │ │ +terminate(_Args, Fd) -> │ │ │ │ + file:close(Fd).

The code is explained in the next sections.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Starting an Event Manager │ │ │ │

│ │ │ │

To start an event manager for handling errors, as described in the previous │ │ │ │ example, call the following function:

gen_event:start_link({local, error_man})

gen_event:start_link/1 spawns and links to a new event manager process.

The argument, {local, error_man}, specifies the name under which the │ │ │ │ @@ -89,57 +89,57 @@ │ │ │ │ manager that is not part of a supervision tree.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Adding an Event Handler │ │ │ │

│ │ │ │

The following example shows how to start an event manager and add an event │ │ │ │ -handler to it by using the shell:

1> gen_event:start({local, error_man}).
│ │ │ │ -{ok,<0.31.0>}
│ │ │ │ -2> gen_event:add_handler(error_man, terminal_logger, []).
│ │ │ │ +handler to it by using the shell:

1> gen_event:start({local, error_man}).
│ │ │ │ +{ok,<0.31.0>}
│ │ │ │ +2> gen_event:add_handler(error_man, terminal_logger, []).
│ │ │ │  ok

This function sends a message to the event manager registered as error_man, │ │ │ │ telling it to add the event handler terminal_logger. The event manager calls │ │ │ │ the callback function terminal_logger:init([]), where the argument [] is the │ │ │ │ third argument to add_handler. init/1 is expected to return {ok, State}, │ │ │ │ -where State is the internal state of the event handler.

init(_Args) ->
│ │ │ │ -    {ok, []}.

Here, init/1 does not need any input data and ignores its argument. For │ │ │ │ +where State is the internal state of the event handler.

init(_Args) ->
│ │ │ │ +    {ok, []}.

Here, init/1 does not need any input data and ignores its argument. For │ │ │ │ terminal_logger, the internal state is not used. For file_logger, the │ │ │ │ -internal state is used to save the open file descriptor.

init(File) ->
│ │ │ │ -    {ok, Fd} = file:open(File, read),
│ │ │ │ -    {ok, Fd}.

│ │ │ │ +internal state is used to save the open file descriptor.

init(File) ->
│ │ │ │ +    {ok, Fd} = file:open(File, read),
│ │ │ │ +    {ok, Fd}.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Notifying about Events │ │ │ │

│ │ │ │
3> gen_event:notify(error_man, no_reply).
│ │ │ │  ***Error*** no_reply
│ │ │ │  ok

error_man is the name of the event manager and no_reply is the event.

The event is made into a message and sent to the event manager. When the event │ │ │ │ is received, the event manager calls handle_event(Event, State) for each │ │ │ │ installed event handler, in the same order as they were added. The function is │ │ │ │ expected to return a tuple {ok,State1}, where State1 is a new value for the │ │ │ │ -state of the event handler.

In terminal_logger:

handle_event(ErrorMsg, State) ->
│ │ │ │ -    io:format("***Error*** ~p~n", [ErrorMsg]),
│ │ │ │ -    {ok, State}.

In file_logger:

handle_event(ErrorMsg, Fd) ->
│ │ │ │ -    io:format(Fd, "***Error*** ~p~n", [ErrorMsg]),
│ │ │ │ -    {ok, Fd}.

│ │ │ │ +state of the event handler.

In terminal_logger:

handle_event(ErrorMsg, State) ->
│ │ │ │ +    io:format("***Error*** ~p~n", [ErrorMsg]),
│ │ │ │ +    {ok, State}.

In file_logger:

handle_event(ErrorMsg, Fd) ->
│ │ │ │ +    io:format(Fd, "***Error*** ~p~n", [ErrorMsg]),
│ │ │ │ +    {ok, Fd}.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Deleting an Event Handler │ │ │ │

│ │ │ │ -
4> gen_event:delete_handler(error_man, terminal_logger, []).
│ │ │ │ +
4> gen_event:delete_handler(error_man, terminal_logger, []).
│ │ │ │  ok

This function sends a message to the event manager registered as error_man, │ │ │ │ telling it to delete the event handler terminal_logger. The event manager │ │ │ │ calls the callback function terminal_logger:terminate([], State), where the │ │ │ │ argument [] is the third argument to delete_handler. terminate/2 is to be │ │ │ │ the opposite of init/1 and do any necessary cleaning up. Its return value is │ │ │ │ -ignored.

For terminal_logger, no cleaning up is necessary:

terminate(_Args, _State) ->
│ │ │ │ -    ok.

For file_logger, the file descriptor opened in init must be closed:

terminate(_Args, Fd) ->
│ │ │ │ -    file:close(Fd).

│ │ │ │ +ignored.

For terminal_logger, no cleaning up is necessary:

terminate(_Args, _State) ->
│ │ │ │ +    ok.

For file_logger, the file descriptor opened in init must be closed:

terminate(_Args, Fd) ->
│ │ │ │ +    file:close(Fd).

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Stopping │ │ │ │

│ │ │ │

When an event manager is stopped, it gives each of the installed event handlers │ │ │ │ the chance to clean up by calling terminate/2, the same way as when deleting a │ │ │ │ @@ -154,29 +154,29 @@ │ │ │ │ this is done is defined by a shutdown strategy set in │ │ │ │ the supervisor.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Standalone Event Managers │ │ │ │

│ │ │ │ -

An event manager can also be stopped by calling:

1> gen_event:stop(error_man).
│ │ │ │ +

An event manager can also be stopped by calling:

1> gen_event:stop(error_man).
│ │ │ │  ok

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Handling Other Messages │ │ │ │

│ │ │ │

If the gen_event process is to be able to receive other messages │ │ │ │ than events, the callback function handle_info(Info, State) must be │ │ │ │ implemented to handle them. Examples of other messages are exit │ │ │ │ messages if the event manager is linked to other processes than the │ │ │ │ supervisor (for example via gen_event:add_sup_handler/3) and is │ │ │ │ -trapping exit signals.

handle_info({'EXIT', Pid, Reason}, State) ->
│ │ │ │ +trapping exit signals.

handle_info({'EXIT', Pid, Reason}, State) ->
│ │ │ │      %% Code to handle exits here.
│ │ │ │      ...
│ │ │ │ -    {noreply, State1}.

The final function to implement is code_change/3:

code_change(OldVsn, State, Extra) ->
│ │ │ │ +    {noreply, State1}.

The final function to implement is code_change/3:

code_change(OldVsn, State, Extra) ->
│ │ │ │      %% Code to convert state (and more) during code change.
│ │ │ │      ...
│ │ │ │ -    {ok, NewState}.
│ │ │ │ +
{ok, NewState}.
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/errors.xhtml │ │ │ │ @@ -56,22 +56,22 @@ │ │ │ │ classes, with different origins. The try expression can │ │ │ │ distinguish between the different classes, whereas the │ │ │ │ catch expression cannot. try and catch are described │ │ │ │ in Expressions.

ClassOrigin
errorRun-time error, for example, 1+a, or the process called error/1
exitThe process called exit/1
throwThe process called throw/1

Table: Exception Classes.

All of the above exceptions can also be generated by calling erlang:raise/3.

An exception consists of its class, an exit reason (see │ │ │ │ Exit Reason), and a stack trace (which aids in finding │ │ │ │ the code location of the exception).

The stack trace can be bound to a variable from within a try expression for │ │ │ │ any exception class, or as part of the exit reason when a run-time error is │ │ │ │ -caught by a catch. Example:

> {'EXIT',{test,Stacktrace}} = (catch error(test)), Stacktrace.
│ │ │ │ -[{shell,apply_fun,3,[]},
│ │ │ │ - {erl_eval,do_apply,6,[]},
│ │ │ │ - ...]
│ │ │ │ -> try throw(test) catch Class:Reason:Stacktrace -> Stacktrace end.
│ │ │ │ -[{shell,apply_fun,3,[]},
│ │ │ │ - {erl_eval,do_apply,6,[]},
│ │ │ │ - ...]

│ │ │ │ +caught by a catch. Example:

> {'EXIT',{test,Stacktrace}} = (catch error(test)), Stacktrace.
│ │ │ │ +[{shell,apply_fun,3,[]},
│ │ │ │ + {erl_eval,do_apply,6,[]},
│ │ │ │ + ...]
│ │ │ │ +> try throw(test) catch Class:Reason:Stacktrace -> Stacktrace end.
│ │ │ │ +[{shell,apply_fun,3,[]},
│ │ │ │ + {erl_eval,do_apply,6,[]},
│ │ │ │ + ...]

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ The call-stack back trace (stacktrace) │ │ │ │

│ │ │ │

The stack back-trace (stacktrace) is a list that │ │ │ │ contains {Module, Function, Arity, ExtraInfo} and/or {Fun, Arity, ExtraInfo} │ │ │ ├── OEBPS/error_logging.xhtml │ │ │ │ @@ -48,36 +48,36 @@ │ │ │ │ reports and other error and information reports are by default logged through │ │ │ │ the log handler which is set up when the Kernel application is started.

Prior to Erlang/OTP 21.0, supervisor, crash, and progress reports were only │ │ │ │ logged when the SASL application was running. This behaviour can, for backwards │ │ │ │ compatibility, be enabled by setting the Kernel configuration parameter │ │ │ │ logger_sasl_compatible to │ │ │ │ true. For more information, see │ │ │ │ SASL Error Logging in the SASL User's Guide.

% erl -kernel logger_level info
│ │ │ │ -Erlang/OTP 21 [erts-10.0] [source-13c50db] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]
│ │ │ │ +Erlang/OTP 21 [erts-10.0] [source-13c50db] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]
│ │ │ │  
│ │ │ │  =PROGRESS REPORT==== 8-Jun-2018::16:54:19.916404 ===
│ │ │ │      application: kernel
│ │ │ │      started_at: nonode@nohost
│ │ │ │  =PROGRESS REPORT==== 8-Jun-2018::16:54:19.922908 ===
│ │ │ │      application: stdlib
│ │ │ │      started_at: nonode@nohost
│ │ │ │  =PROGRESS REPORT==== 8-Jun-2018::16:54:19.925755 ===
│ │ │ │ -    supervisor: {local,kernel_safe_sup}
│ │ │ │ -    started: [{pid,<0.74.0>},
│ │ │ │ -              {id,disk_log_sup},
│ │ │ │ -              {mfargs,{disk_log_sup,start_link,[]}},
│ │ │ │ -              {restart_type,permanent},
│ │ │ │ -              {shutdown,1000},
│ │ │ │ -              {child_type,supervisor}]
│ │ │ │ +    supervisor: {local,kernel_safe_sup}
│ │ │ │ +    started: [{pid,<0.74.0>},
│ │ │ │ +              {id,disk_log_sup},
│ │ │ │ +              {mfargs,{disk_log_sup,start_link,[]}},
│ │ │ │ +              {restart_type,permanent},
│ │ │ │ +              {shutdown,1000},
│ │ │ │ +              {child_type,supervisor}]
│ │ │ │  =PROGRESS REPORT==== 8-Jun-2018::16:54:19.926056 ===
│ │ │ │ -    supervisor: {local,kernel_safe_sup}
│ │ │ │ -    started: [{pid,<0.75.0>},
│ │ │ │ -              {id,disk_log_server},
│ │ │ │ -              {mfargs,{disk_log_server,start_link,[]}},
│ │ │ │ -              {restart_type,permanent},
│ │ │ │ -              {shutdown,2000},
│ │ │ │ -              {child_type,worker}]
│ │ │ │ -Eshell V10.0  (abort with ^G)
│ │ │ │ +    supervisor: {local,kernel_safe_sup}
│ │ │ │ +    started: [{pid,<0.75.0>},
│ │ │ │ +              {id,disk_log_server},
│ │ │ │ +              {mfargs,{disk_log_server,start_link,[]}},
│ │ │ │ +              {restart_type,permanent},
│ │ │ │ +              {shutdown,2000},
│ │ │ │ +              {child_type,worker}]
│ │ │ │ +Eshell V10.0  (abort with ^G)
│ │ │ │  1>
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/erl_interface.xhtml │ │ │ │ @@ -25,119 +25,119 @@ │ │ │ │ to read the port example in Ports before reading this section.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Erlang Program │ │ │ │

│ │ │ │

The following example shows an Erlang program communicating with a C program │ │ │ │ -over a plain port with home made encoding:

-module(complex1).
│ │ │ │ --export([start/1, stop/0, init/1]).
│ │ │ │ --export([foo/1, bar/1]).
│ │ │ │ -
│ │ │ │ -start(ExtPrg) ->
│ │ │ │ -    spawn(?MODULE, init, [ExtPrg]).
│ │ │ │ -stop() ->
│ │ │ │ +over a plain port with home made encoding:

-module(complex1).
│ │ │ │ +-export([start/1, stop/0, init/1]).
│ │ │ │ +-export([foo/1, bar/1]).
│ │ │ │ +
│ │ │ │ +start(ExtPrg) ->
│ │ │ │ +    spawn(?MODULE, init, [ExtPrg]).
│ │ │ │ +stop() ->
│ │ │ │      complex ! stop.
│ │ │ │  
│ │ │ │ -foo(X) ->
│ │ │ │ -    call_port({foo, X}).
│ │ │ │ -bar(Y) ->
│ │ │ │ -    call_port({bar, Y}).
│ │ │ │ +foo(X) ->
│ │ │ │ +    call_port({foo, X}).
│ │ │ │ +bar(Y) ->
│ │ │ │ +    call_port({bar, Y}).
│ │ │ │  
│ │ │ │ -call_port(Msg) ->
│ │ │ │ -    complex ! {call, self(), Msg},
│ │ │ │ +call_port(Msg) ->
│ │ │ │ +    complex ! {call, self(), Msg},
│ │ │ │      receive
│ │ │ │ -	{complex, Result} ->
│ │ │ │ +	{complex, Result} ->
│ │ │ │  	    Result
│ │ │ │      end.
│ │ │ │  
│ │ │ │ -init(ExtPrg) ->
│ │ │ │ -    register(complex, self()),
│ │ │ │ -    process_flag(trap_exit, true),
│ │ │ │ -    Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
│ │ │ │ -    loop(Port).
│ │ │ │ +init(ExtPrg) ->
│ │ │ │ +    register(complex, self()),
│ │ │ │ +    process_flag(trap_exit, true),
│ │ │ │ +    Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
│ │ │ │ +    loop(Port).
│ │ │ │  
│ │ │ │ -loop(Port) ->
│ │ │ │ +loop(Port) ->
│ │ │ │      receive
│ │ │ │ -	{call, Caller, Msg} ->
│ │ │ │ -	    Port ! {self(), {command, encode(Msg)}},
│ │ │ │ +	{call, Caller, Msg} ->
│ │ │ │ +	    Port ! {self(), {command, encode(Msg)}},
│ │ │ │  	    receive
│ │ │ │ -		{Port, {data, Data}} ->
│ │ │ │ -		    Caller ! {complex, decode(Data)}
│ │ │ │ +		{Port, {data, Data}} ->
│ │ │ │ +		    Caller ! {complex, decode(Data)}
│ │ │ │  	    end,
│ │ │ │ -	    loop(Port);
│ │ │ │ +	    loop(Port);
│ │ │ │  	stop ->
│ │ │ │ -	    Port ! {self(), close},
│ │ │ │ +	    Port ! {self(), close},
│ │ │ │  	    receive
│ │ │ │ -		{Port, closed} ->
│ │ │ │ -		    exit(normal)
│ │ │ │ +		{Port, closed} ->
│ │ │ │ +		    exit(normal)
│ │ │ │  	    end;
│ │ │ │ -	{'EXIT', Port, Reason} ->
│ │ │ │ -	    exit(port_terminated)
│ │ │ │ +	{'EXIT', Port, Reason} ->
│ │ │ │ +	    exit(port_terminated)
│ │ │ │      end.
│ │ │ │  
│ │ │ │ -encode({foo, X}) -> [1, X];
│ │ │ │ -encode({bar, Y}) -> [2, Y].
│ │ │ │ +encode({foo, X}) -> [1, X];
│ │ │ │ +encode({bar, Y}) -> [2, Y].
│ │ │ │  
│ │ │ │ -decode([Int]) -> Int.

There are two differences when using Erl_Interface on the C side compared to the │ │ │ │ +decode([Int]) -> Int.

There are two differences when using Erl_Interface on the C side compared to the │ │ │ │ example in Ports, using only the plain port:

  • As Erl_Interface operates on the Erlang external term format, the port must be │ │ │ │ set to use binaries.
  • Instead of inventing an encoding/decoding scheme, the │ │ │ │ term_to_binary/1 and │ │ │ │ -binary_to_term/1 BIFs are to be used.

That is:

open_port({spawn, ExtPrg}, [{packet, 2}])

is replaced with:

open_port({spawn, ExtPrg}, [{packet, 2}, binary])

And:

Port ! {self(), {command, encode(Msg)}},
│ │ │ │ +binary_to_term/1 BIFs are to be used.

That is:

open_port({spawn, ExtPrg}, [{packet, 2}])

is replaced with:

open_port({spawn, ExtPrg}, [{packet, 2}, binary])

And:

Port ! {self(), {command, encode(Msg)}},
│ │ │ │  receive
│ │ │ │ -  {Port, {data, Data}} ->
│ │ │ │ -    Caller ! {complex, decode(Data)}
│ │ │ │ -end

is replaced with:

Port ! {self(), {command, term_to_binary(Msg)}},
│ │ │ │ +  {Port, {data, Data}} ->
│ │ │ │ +    Caller ! {complex, decode(Data)}
│ │ │ │ +end

is replaced with:

Port ! {self(), {command, term_to_binary(Msg)}},
│ │ │ │  receive
│ │ │ │ -  {Port, {data, Data}} ->
│ │ │ │ -    Caller ! {complex, binary_to_term(Data)}
│ │ │ │ -end

The resulting Erlang program is as follows:

-module(complex2).
│ │ │ │ --export([start/1, stop/0, init/1]).
│ │ │ │ --export([foo/1, bar/1]).
│ │ │ │ -
│ │ │ │ -start(ExtPrg) ->
│ │ │ │ -    spawn(?MODULE, init, [ExtPrg]).
│ │ │ │ -stop() ->
│ │ │ │ +  {Port, {data, Data}} ->
│ │ │ │ +    Caller ! {complex, binary_to_term(Data)}
│ │ │ │ +end

The resulting Erlang program is as follows:

-module(complex2).
│ │ │ │ +-export([start/1, stop/0, init/1]).
│ │ │ │ +-export([foo/1, bar/1]).
│ │ │ │ +
│ │ │ │ +start(ExtPrg) ->
│ │ │ │ +    spawn(?MODULE, init, [ExtPrg]).
│ │ │ │ +stop() ->
│ │ │ │      complex ! stop.
│ │ │ │  
│ │ │ │ -foo(X) ->
│ │ │ │ -    call_port({foo, X}).
│ │ │ │ -bar(Y) ->
│ │ │ │ -    call_port({bar, Y}).
│ │ │ │ +foo(X) ->
│ │ │ │ +    call_port({foo, X}).
│ │ │ │ +bar(Y) ->
│ │ │ │ +    call_port({bar, Y}).
│ │ │ │  
│ │ │ │ -call_port(Msg) ->
│ │ │ │ -    complex ! {call, self(), Msg},
│ │ │ │ +call_port(Msg) ->
│ │ │ │ +    complex ! {call, self(), Msg},
│ │ │ │      receive
│ │ │ │ -	{complex, Result} ->
│ │ │ │ +	{complex, Result} ->
│ │ │ │  	    Result
│ │ │ │      end.
│ │ │ │  
│ │ │ │ -init(ExtPrg) ->
│ │ │ │ -    register(complex, self()),
│ │ │ │ -    process_flag(trap_exit, true),
│ │ │ │ -    Port = open_port({spawn, ExtPrg}, [{packet, 2}, binary]),
│ │ │ │ -    loop(Port).
│ │ │ │ +init(ExtPrg) ->
│ │ │ │ +    register(complex, self()),
│ │ │ │ +    process_flag(trap_exit, true),
│ │ │ │ +    Port = open_port({spawn, ExtPrg}, [{packet, 2}, binary]),
│ │ │ │ +    loop(Port).
│ │ │ │  
│ │ │ │ -loop(Port) ->
│ │ │ │ +loop(Port) ->
│ │ │ │      receive
│ │ │ │ -	{call, Caller, Msg} ->
│ │ │ │ -	    Port ! {self(), {command, term_to_binary(Msg)}},
│ │ │ │ +	{call, Caller, Msg} ->
│ │ │ │ +	    Port ! {self(), {command, term_to_binary(Msg)}},
│ │ │ │  	    receive
│ │ │ │ -		{Port, {data, Data}} ->
│ │ │ │ -		    Caller ! {complex, binary_to_term(Data)}
│ │ │ │ +		{Port, {data, Data}} ->
│ │ │ │ +		    Caller ! {complex, binary_to_term(Data)}
│ │ │ │  	    end,
│ │ │ │ -	    loop(Port);
│ │ │ │ +	    loop(Port);
│ │ │ │  	stop ->
│ │ │ │ -	    Port ! {self(), close},
│ │ │ │ +	    Port ! {self(), close},
│ │ │ │  	    receive
│ │ │ │ -		{Port, closed} ->
│ │ │ │ -		    exit(normal)
│ │ │ │ +		{Port, closed} ->
│ │ │ │ +		    exit(normal)
│ │ │ │  	    end;
│ │ │ │ -	{'EXIT', Port, Reason} ->
│ │ │ │ -	    exit(port_terminated)
│ │ │ │ +	{'EXIT', Port, Reason} ->
│ │ │ │ +	    exit(port_terminated)
│ │ │ │      end.

Notice that calling complex2:foo/1 and complex2:bar/1 results in the tuple │ │ │ │ {foo,X} or {bar,Y} being sent to the complex process, which codes them as │ │ │ │ binaries and sends them to the port. This means that the C program must be able │ │ │ │ to handle these two tuples.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -267,24 +267,24 @@ │ │ │ │ -L/usr/local/otp/lib/erl_interface-3.9.2/lib \ │ │ │ │ complex.c erl_comm.c ei.c -lei -lpthread

In Erlang/OTP R5B and later versions of OTP, the include and lib directories │ │ │ │ are situated under $OTPROOT/lib/erl_interface-VSN, where $OTPROOT is the │ │ │ │ root directory of the OTP installation (/usr/local/otp in the recent example) │ │ │ │ and VSN is the version of the Erl_interface application (3.2.1 in the recent │ │ │ │ example).

In R4B and earlier versions of OTP, include and lib are situated under │ │ │ │ $OTPROOT/usr.

Step 2. Start Erlang and compile the Erlang code:

$ erl
│ │ │ │ -Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
│ │ │ │ +Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
│ │ │ │  
│ │ │ │ -Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
│ │ │ │ -1> c(complex2).
│ │ │ │ -{ok,complex2}

Step 3. Run the example:

2> complex2:start("./extprg").
│ │ │ │ +Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
│ │ │ │ +1> c(complex2).
│ │ │ │ +{ok,complex2}

Step 3. Run the example:

2> complex2:start("./extprg").
│ │ │ │  <0.34.0>
│ │ │ │ -3> complex2:foo(3).
│ │ │ │ +3> complex2:foo(3).
│ │ │ │  4
│ │ │ │ -4> complex2:bar(5).
│ │ │ │ +4> complex2:bar(5).
│ │ │ │  10
│ │ │ │ -5> complex2:bar(352).
│ │ │ │ +5> complex2:bar(352).
│ │ │ │  704
│ │ │ │ -6> complex2:stop().
│ │ │ │ +6> complex2:stop().
│ │ │ │  stop
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/eff_guide_processes.xhtml │ │ │ │ @@ -24,45 +24,45 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Creating an Erlang Process │ │ │ │

│ │ │ │

An Erlang process is lightweight compared to threads and processes in operating │ │ │ │ systems.

A newly spawned Erlang process uses 327 words of memory. The size can be found │ │ │ │ -as follows:

Erlang/OTP 27 [erts-14.2.3] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
│ │ │ │ +as follows:

Erlang/OTP 27 [erts-14.2.3] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
│ │ │ │  
│ │ │ │ -Eshell V14.2.3 (press Ctrl+G to abort, type help(). for help)
│ │ │ │ -1> Fun = fun() -> receive after infinity -> ok end end.
│ │ │ │ +Eshell V14.2.3 (press Ctrl+G to abort, type help(). for help)
│ │ │ │ +1> Fun = fun() -> receive after infinity -> ok end end.
│ │ │ │  #Fun<erl_eval.43.39164016>
│ │ │ │ -2> {_,Bytes} = process_info(spawn(Fun), memory).
│ │ │ │ -{memory,2616}
│ │ │ │ -3> Bytes div erlang:system_info(wordsize).
│ │ │ │ +2> {_,Bytes} = process_info(spawn(Fun), memory).
│ │ │ │ +{memory,2616}
│ │ │ │ +3> Bytes div erlang:system_info(wordsize).
│ │ │ │  327

The size includes 233 words for the heap area (which includes the stack). The │ │ │ │ garbage collector increases the heap as needed.

The main (outer) loop for a process must be tail-recursive. Otherwise, the │ │ │ │ -stack grows until the process terminates.

DO NOT

loop() ->
│ │ │ │ +stack grows until the process terminates.

DO NOT

loop() ->
│ │ │ │    receive
│ │ │ │ -     {sys, Msg} ->
│ │ │ │ -         handle_sys_msg(Msg),
│ │ │ │ -         loop();
│ │ │ │ -     {From, Msg} ->
│ │ │ │ -          Reply = handle_msg(Msg),
│ │ │ │ +     {sys, Msg} ->
│ │ │ │ +         handle_sys_msg(Msg),
│ │ │ │ +         loop();
│ │ │ │ +     {From, Msg} ->
│ │ │ │ +          Reply = handle_msg(Msg),
│ │ │ │            From ! Reply,
│ │ │ │ -          loop()
│ │ │ │ +          loop()
│ │ │ │    end,
│ │ │ │ -  io:format("Message is processed~n", []).

The call to io:format/2 will never be executed, but a return address will │ │ │ │ + io:format("Message is processed~n", []).

The call to io:format/2 will never be executed, but a return address will │ │ │ │ still be pushed to the stack each time loop/0 is called recursively. The │ │ │ │ -correct tail-recursive version of the function looks as follows:

DO

loop() ->
│ │ │ │ +correct tail-recursive version of the function looks as follows:

DO

loop() ->
│ │ │ │     receive
│ │ │ │ -      {sys, Msg} ->
│ │ │ │ -         handle_sys_msg(Msg),
│ │ │ │ -         loop();
│ │ │ │ -      {From, Msg} ->
│ │ │ │ -         Reply = handle_msg(Msg),
│ │ │ │ +      {sys, Msg} ->
│ │ │ │ +         handle_sys_msg(Msg),
│ │ │ │ +         loop();
│ │ │ │ +      {From, Msg} ->
│ │ │ │ +         Reply = handle_msg(Msg),
│ │ │ │           From ! Reply,
│ │ │ │ -         loop()
│ │ │ │ +         loop()
│ │ │ │   end.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Initial Heap Size │ │ │ │

│ │ │ │

The default initial heap size of 233 words is quite conservative to support │ │ │ │ @@ -95,30 +95,30 @@ │ │ │ │ │ │ │ │ Fetching Received Messages │ │ │ │ │ │ │ │

The cost of fetching a received message from the message queue depends on how │ │ │ │ complicated the receive expression is. A simple expression that matches any │ │ │ │ message is very cheap because it retrieves the first message in the message │ │ │ │ queue:

DO

receive
│ │ │ │ -    Message -> handle_msg(Message)
│ │ │ │ +    Message -> handle_msg(Message)
│ │ │ │  end.

However, this is not always convenient: we can receive a message that we do not │ │ │ │ know how to handle at this point, so it is common to only match the messages we │ │ │ │ expect:

receive
│ │ │ │ -    {Tag, Message} -> handle_msg(Message)
│ │ │ │ +    {Tag, Message} -> handle_msg(Message)
│ │ │ │  end.

While this is convenient it means that the entire message queue must be searched │ │ │ │ until it finds a matching message. This is very expensive for processes with │ │ │ │ long message queues, so there is an optimization for the common case of │ │ │ │ -sending a request and waiting for a response shortly after:

DO

MRef = monitor(process, Process),
│ │ │ │ -Process ! {self(), MRef, Request},
│ │ │ │ +sending a request and waiting for a response shortly after:

DO

MRef = monitor(process, Process),
│ │ │ │ +Process ! {self(), MRef, Request},
│ │ │ │  receive
│ │ │ │ -    {MRef, Reply} ->
│ │ │ │ -        erlang:demonitor(MRef, [flush]),
│ │ │ │ -        handle_reply(Reply);
│ │ │ │ -    {'DOWN', MRef, _, _, Reason} ->
│ │ │ │ -        handle_error(Reason)
│ │ │ │ +    {MRef, Reply} ->
│ │ │ │ +        erlang:demonitor(MRef, [flush]),
│ │ │ │ +        handle_reply(Reply);
│ │ │ │ +    {'DOWN', MRef, _, _, Reason} ->
│ │ │ │ +        handle_error(Reason)
│ │ │ │  end.

Since the compiler knows that the reference created by │ │ │ │ monitor/2 cannot exist before the call (since it is a globally │ │ │ │ unique identifier), and that the receive only matches messages that contain │ │ │ │ said reference, it will tell the emulator to search only the messages that │ │ │ │ arrived after the call to monitor/2.

The above is a simple example where one is guaranteed that the optimization │ │ │ │ will take, but what about more complicated code?

│ │ │ │ │ │ │ │ @@ -134,101 +134,101 @@ │ │ │ │ efficiency_guide.erl:200: Warning: NOT OPTIMIZED: all clauses do not match a suitable reference │ │ │ │ efficiency_guide.erl:206: Warning: OPTIMIZED: reference used to mark a message queue position │ │ │ │ efficiency_guide.erl:208: Warning: OPTIMIZED: all clauses match reference created by monitor/2 at efficiency_guide.erl:206 │ │ │ │ efficiency_guide.erl:219: Warning: INFO: passing reference created by make_ref/0 at efficiency_guide.erl:218 │ │ │ │ efficiency_guide.erl:222: Warning: OPTIMIZED: all clauses match reference in function parameter 1

To make it clearer exactly what code the warnings refer to, the warnings in the │ │ │ │ following examples are inserted as comments after the clause they refer to, for │ │ │ │ example:

%% DO
│ │ │ │ -simple_receive() ->
│ │ │ │ +simple_receive() ->
│ │ │ │  %% efficiency_guide.erl:194: Warning: INFO: not a selective receive, this is always fast
│ │ │ │  receive
│ │ │ │ -    Message -> handle_msg(Message)
│ │ │ │ +    Message -> handle_msg(Message)
│ │ │ │  end.
│ │ │ │  
│ │ │ │  %% DO NOT, unless Tag is known to be a suitable reference: see
│ │ │ │  %% cross_function_receive/0 further down.
│ │ │ │ -selective_receive(Tag, Message) ->
│ │ │ │ +selective_receive(Tag, Message) ->
│ │ │ │  %% efficiency_guide.erl:200: Warning: NOT OPTIMIZED: all clauses do not match a suitable reference
│ │ │ │  receive
│ │ │ │ -    {Tag, Message} -> handle_msg(Message)
│ │ │ │ +    {Tag, Message} -> handle_msg(Message)
│ │ │ │  end.
│ │ │ │  
│ │ │ │  %% DO
│ │ │ │ -optimized_receive(Process, Request) ->
│ │ │ │ +optimized_receive(Process, Request) ->
│ │ │ │  %% efficiency_guide.erl:206: Warning: OPTIMIZED: reference used to mark a message queue position
│ │ │ │ -    MRef = monitor(process, Process),
│ │ │ │ -    Process ! {self(), MRef, Request},
│ │ │ │ +    MRef = monitor(process, Process),
│ │ │ │ +    Process ! {self(), MRef, Request},
│ │ │ │      %% efficiency_guide.erl:208: Warning: OPTIMIZED: matches reference created by monitor/2 at efficiency_guide.erl:206
│ │ │ │      receive
│ │ │ │ -        {MRef, Reply} ->
│ │ │ │ -        erlang:demonitor(MRef, [flush]),
│ │ │ │ -        handle_reply(Reply);
│ │ │ │ -    {'DOWN', MRef, _, _, Reason} ->
│ │ │ │ -    handle_error(Reason)
│ │ │ │ +        {MRef, Reply} ->
│ │ │ │ +        erlang:demonitor(MRef, [flush]),
│ │ │ │ +        handle_reply(Reply);
│ │ │ │ +    {'DOWN', MRef, _, _, Reason} ->
│ │ │ │ +    handle_error(Reason)
│ │ │ │      end.
│ │ │ │  
│ │ │ │  %% DO
│ │ │ │ -cross_function_receive() ->
│ │ │ │ +cross_function_receive() ->
│ │ │ │      %% efficiency_guide.erl:218: Warning: OPTIMIZED: reference used to mark a message queue position
│ │ │ │ -    Ref = make_ref(),
│ │ │ │ +    Ref = make_ref(),
│ │ │ │      %% efficiency_guide.erl:219: Warning: INFO: passing reference created by make_ref/0 at efficiency_guide.erl:218
│ │ │ │ -    cross_function_receive(Ref).
│ │ │ │ +    cross_function_receive(Ref).
│ │ │ │  
│ │ │ │ -cross_function_receive(Ref) ->
│ │ │ │ +cross_function_receive(Ref) ->
│ │ │ │      %% efficiency_guide.erl:222: Warning: OPTIMIZED: all clauses match reference in function parameter 1
│ │ │ │      receive
│ │ │ │ -        {Ref, Message} -> handle_msg(Message)
│ │ │ │ +        {Ref, Message} -> handle_msg(Message)
│ │ │ │      end.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Literal Pool │ │ │ │

│ │ │ │

Constant Erlang terms (hereafter called literals) are kept in literal pools; │ │ │ │ each loaded module has its own pool. The following function does not build the │ │ │ │ tuple every time it is called (only to have it discarded the next time the │ │ │ │ garbage collector was run), but the tuple is located in the module's literal │ │ │ │ -pool:

DO

days_in_month(M) ->
│ │ │ │ -    element(M, {31,28,31,30,31,30,31,31,30,31,30,31}).

If a literal, or a term that contains a literal, is inserted into an Ets table, │ │ │ │ +pool:

DO

days_in_month(M) ->
│ │ │ │ +    element(M, {31,28,31,30,31,30,31,31,30,31,30,31}).

If a literal, or a term that contains a literal, is inserted into an Ets table, │ │ │ │ it is copied. The reason is that the module containing the literal can be │ │ │ │ unloaded in the future.

When a literal is sent to another process, it is not copied. When a module │ │ │ │ holding a literal is unloaded, the literal will be copied to the heap of all │ │ │ │ processes that hold references to that literal.

There also exists a global literal pool that is managed by the │ │ │ │ persistent_term module.

By default, 1 GB of virtual address space is reserved for all literal pools (in │ │ │ │ BEAM code and persistent terms). The amount of virtual address space reserved │ │ │ │ for literals can be changed by using the │ │ │ │ +MIscs option when starting the emulator.

Here is an example how the reserved virtual address space for literals can be │ │ │ │ raised to 2 GB (2048 MB):

erl +MIscs 2048

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Loss of Sharing │ │ │ │

│ │ │ │ -

An Erlang term can have shared subterms. Here is a simple example:

{SubTerm, SubTerm}

Shared subterms are not preserved in the following cases:

  • When a term is sent to another process
  • When a term is passed as the initial process arguments in the spawn call
  • When a term is stored in an Ets table

That is an optimization. Most applications do not send messages with shared │ │ │ │ -subterms.

The following example shows how a shared subterm can be created:

kilo_byte() ->
│ │ │ │ -    kilo_byte(10, [42]).
│ │ │ │ +

An Erlang term can have shared subterms. Here is a simple example:

{SubTerm, SubTerm}

Shared subterms are not preserved in the following cases:

  • When a term is sent to another process
  • When a term is passed as the initial process arguments in the spawn call
  • When a term is stored in an Ets table

That is an optimization. Most applications do not send messages with shared │ │ │ │ +subterms.

The following example shows how a shared subterm can be created:

kilo_byte() ->
│ │ │ │ +    kilo_byte(10, [42]).
│ │ │ │  
│ │ │ │ -kilo_byte(0, Acc) ->
│ │ │ │ +kilo_byte(0, Acc) ->
│ │ │ │      Acc;
│ │ │ │ -kilo_byte(N, Acc) ->
│ │ │ │ -    kilo_byte(N-1, [Acc|Acc]).

kilo_byte/1 creates a deep list. If list_to_binary/1 │ │ │ │ +kilo_byte(N, Acc) -> │ │ │ │ + kilo_byte(N-1, [Acc|Acc]).

kilo_byte/1 creates a deep list. If list_to_binary/1 │ │ │ │ is called, the deep list can be converted to a binary of 1024 bytes:

1> byte_size(list_to_binary(efficiency_guide:kilo_byte())).
│ │ │ │  1024

Using the erts_debug:size/1 BIF, it can be seen that the deep list only │ │ │ │ -requires 22 words of heap space:

2> erts_debug:size(efficiency_guide:kilo_byte()).
│ │ │ │ +requires 22 words of heap space:

2> erts_debug:size(efficiency_guide:kilo_byte()).
│ │ │ │  22

Using the erts_debug:flat_size/1 BIF, the size of the deep list can be │ │ │ │ calculated if sharing is ignored. It becomes the size of the list when it has │ │ │ │ -been sent to another process or stored in an Ets table:

3> erts_debug:flat_size(efficiency_guide:kilo_byte()).
│ │ │ │ +been sent to another process or stored in an Ets table:

3> erts_debug:flat_size(efficiency_guide:kilo_byte()).
│ │ │ │  4094

It can be verified that sharing will be lost if the data is inserted into an Ets │ │ │ │ -table:

4> T = ets:new(tab, []).
│ │ │ │ +table:

4> T = ets:new(tab, []).
│ │ │ │  #Ref<0.1662103692.2407923716.214181>
│ │ │ │ -5> ets:insert(T, {key,efficiency_guide:kilo_byte()}).
│ │ │ │ +5> ets:insert(T, {key,efficiency_guide:kilo_byte()}).
│ │ │ │  true
│ │ │ │ -6> erts_debug:size(element(2, hd(ets:lookup(T, key)))).
│ │ │ │ +6> erts_debug:size(element(2, hd(ets:lookup(T, key)))).
│ │ │ │  4094
│ │ │ │ -7> erts_debug:flat_size(element(2, hd(ets:lookup(T, key)))).
│ │ │ │ +7> erts_debug:flat_size(element(2, hd(ets:lookup(T, key)))).
│ │ │ │  4094

When the data has passed through an Ets table, erts_debug:size/1 and │ │ │ │ erts_debug:flat_size/1 return the same value. Sharing has been lost.

It is possible to build an experimental variant of the runtime system that │ │ │ │ will preserve sharing when copying terms by giving the │ │ │ │ --enable-sharing-preserving option to the configure script.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/eff_guide_functions.xhtml │ │ │ │ @@ -27,67 +27,67 @@ │ │ │ │ Pattern Matching │ │ │ │

│ │ │ │

Pattern matching in function head as well as in case and receive clauses are │ │ │ │ optimized by the compiler. With a few exceptions, there is nothing to gain by │ │ │ │ rearranging clauses.

One exception is pattern matching of binaries. The compiler does not rearrange │ │ │ │ clauses that match binaries. Placing the clause that matches against the empty │ │ │ │ binary last is usually slightly faster than placing it first.

The following is a rather unnatural example to show another exception where │ │ │ │ -rearranging clauses is beneficial:

DO NOT

atom_map1(one) -> 1;
│ │ │ │ -atom_map1(two) -> 2;
│ │ │ │ -atom_map1(three) -> 3;
│ │ │ │ -atom_map1(Int) when is_integer(Int) -> Int;
│ │ │ │ -atom_map1(four) -> 4;
│ │ │ │ -atom_map1(five) -> 5;
│ │ │ │ -atom_map1(six) -> 6.

The problem is the clause with the variable Int. As a variable can match │ │ │ │ +rearranging clauses is beneficial:

DO NOT

atom_map1(one) -> 1;
│ │ │ │ +atom_map1(two) -> 2;
│ │ │ │ +atom_map1(three) -> 3;
│ │ │ │ +atom_map1(Int) when is_integer(Int) -> Int;
│ │ │ │ +atom_map1(four) -> 4;
│ │ │ │ +atom_map1(five) -> 5;
│ │ │ │ +atom_map1(six) -> 6.

The problem is the clause with the variable Int. As a variable can match │ │ │ │ anything, including the atoms four, five, and six, which the following │ │ │ │ clauses also match, the compiler must generate suboptimal code that executes as │ │ │ │ follows:

  • First, the input value is compared to one, two, and three (using a │ │ │ │ single instruction that does a binary search; thus, quite efficient even if │ │ │ │ there are many values) to select which one of the first three clauses to │ │ │ │ execute (if any).
  • If none of the first three clauses match, the fourth clause match as a │ │ │ │ variable always matches.
  • If the guard test is_integer(Int) succeeds, the fourth │ │ │ │ clause is executed.
  • If the guard test fails, the input value is compared to four, five, and │ │ │ │ six, and the appropriate clause is selected. (There is a function_clause │ │ │ │ -exception if none of the values matched.)

Rewriting to either:

DO

atom_map2(one) -> 1;
│ │ │ │ -atom_map2(two) -> 2;
│ │ │ │ -atom_map2(three) -> 3;
│ │ │ │ -atom_map2(four) -> 4;
│ │ │ │ -atom_map2(five) -> 5;
│ │ │ │ -atom_map2(six) -> 6;
│ │ │ │ -atom_map2(Int) when is_integer(Int) -> Int.

or:

DO

atom_map3(Int) when is_integer(Int) -> Int;
│ │ │ │ -atom_map3(one) -> 1;
│ │ │ │ -atom_map3(two) -> 2;
│ │ │ │ -atom_map3(three) -> 3;
│ │ │ │ -atom_map3(four) -> 4;
│ │ │ │ -atom_map3(five) -> 5;
│ │ │ │ -atom_map3(six) -> 6.

gives slightly more efficient matching code.

Another example:

DO NOT

map_pairs1(_Map, [], Ys) ->
│ │ │ │ +exception if none of the values matched.)

Rewriting to either:

DO

atom_map2(one) -> 1;
│ │ │ │ +atom_map2(two) -> 2;
│ │ │ │ +atom_map2(three) -> 3;
│ │ │ │ +atom_map2(four) -> 4;
│ │ │ │ +atom_map2(five) -> 5;
│ │ │ │ +atom_map2(six) -> 6;
│ │ │ │ +atom_map2(Int) when is_integer(Int) -> Int.

or:

DO

atom_map3(Int) when is_integer(Int) -> Int;
│ │ │ │ +atom_map3(one) -> 1;
│ │ │ │ +atom_map3(two) -> 2;
│ │ │ │ +atom_map3(three) -> 3;
│ │ │ │ +atom_map3(four) -> 4;
│ │ │ │ +atom_map3(five) -> 5;
│ │ │ │ +atom_map3(six) -> 6.

gives slightly more efficient matching code.

Another example:

DO NOT

map_pairs1(_Map, [], Ys) ->
│ │ │ │      Ys;
│ │ │ │ -map_pairs1(_Map, Xs, []) ->
│ │ │ │ +map_pairs1(_Map, Xs, []) ->
│ │ │ │      Xs;
│ │ │ │ -map_pairs1(Map, [X|Xs], [Y|Ys]) ->
│ │ │ │ -    [Map(X, Y)|map_pairs1(Map, Xs, Ys)].

The first argument is not a problem. It is variable, but it is a variable in │ │ │ │ +map_pairs1(Map, [X|Xs], [Y|Ys]) -> │ │ │ │ + [Map(X, Y)|map_pairs1(Map, Xs, Ys)].

The first argument is not a problem. It is variable, but it is a variable in │ │ │ │ all clauses. The problem is the variable in the second argument, Xs, in the │ │ │ │ middle clause. Because the variable can match anything, the compiler is not │ │ │ │ allowed to rearrange the clauses, but must generate code that matches them in │ │ │ │ the order written.

If the function is rewritten as follows, the compiler is free to rearrange the │ │ │ │ -clauses:

DO

map_pairs2(_Map, [], Ys) ->
│ │ │ │ +clauses:

DO

map_pairs2(_Map, [], Ys) ->
│ │ │ │      Ys;
│ │ │ │ -map_pairs2(_Map, [_|_]=Xs, [] ) ->
│ │ │ │ +map_pairs2(_Map, [_|_]=Xs, [] ) ->
│ │ │ │      Xs;
│ │ │ │ -map_pairs2(Map, [X|Xs], [Y|Ys]) ->
│ │ │ │ -    [Map(X, Y)|map_pairs2(Map, Xs, Ys)].

The compiler will generate code similar to this:

DO NOT (already done by the compiler)

explicit_map_pairs(Map, Xs0, Ys0) ->
│ │ │ │ +map_pairs2(Map, [X|Xs], [Y|Ys]) ->
│ │ │ │ +    [Map(X, Y)|map_pairs2(Map, Xs, Ys)].

The compiler will generate code similar to this:

DO NOT (already done by the compiler)

explicit_map_pairs(Map, Xs0, Ys0) ->
│ │ │ │      case Xs0 of
│ │ │ │ -	[X|Xs] ->
│ │ │ │ +	[X|Xs] ->
│ │ │ │  	    case Ys0 of
│ │ │ │ -		[Y|Ys] ->
│ │ │ │ -		    [Map(X, Y)|explicit_map_pairs(Map, Xs, Ys)];
│ │ │ │ -		[] ->
│ │ │ │ +		[Y|Ys] ->
│ │ │ │ +		    [Map(X, Y)|explicit_map_pairs(Map, Xs, Ys)];
│ │ │ │ +		[] ->
│ │ │ │  		    Xs0
│ │ │ │  	    end;
│ │ │ │ -	[] ->
│ │ │ │ +	[] ->
│ │ │ │  	    Ys0
│ │ │ │      end.

This is slightly faster for probably the most common case that the input lists │ │ │ │ are not empty or very short. (Another advantage is that Dialyzer can deduce a │ │ │ │ better type for the Xs variable.)

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/drivers.xhtml │ │ │ │ @@ -27,23 +27,23 @@ │ │ │ │ Drivers and Concurrency │ │ │ │

│ │ │ │

The runtime system always takes a lock before running any code in a driver.

By default, that lock is at the driver level, that is, if several ports have │ │ │ │ been opened to the same driver, only code for one port at the same time can be │ │ │ │ running.

A driver can be configured to have one lock for each port instead.

If a driver is used in a functional way (that is, holds no state, but only does │ │ │ │ some heavy calculation and returns a result), several ports with registered │ │ │ │ names can be opened beforehand, and the port to be used can be chosen based on │ │ │ │ -the scheduler ID as follows:

-define(PORT_NAMES(),
│ │ │ │ -	{some_driver_01, some_driver_02, some_driver_03, some_driver_04,
│ │ │ │ +the scheduler ID as follows:

-define(PORT_NAMES(),
│ │ │ │ +	{some_driver_01, some_driver_02, some_driver_03, some_driver_04,
│ │ │ │  	 some_driver_05, some_driver_06, some_driver_07, some_driver_08,
│ │ │ │  	 some_driver_09, some_driver_10, some_driver_11, some_driver_12,
│ │ │ │ -	 some_driver_13, some_driver_14, some_driver_15, some_driver_16}).
│ │ │ │ +	 some_driver_13, some_driver_14, some_driver_15, some_driver_16}).
│ │ │ │  
│ │ │ │ -client_port() ->
│ │ │ │ -    element(erlang:system_info(scheduler_id) rem tuple_size(?PORT_NAMES()) + 1,
│ │ │ │ -	    ?PORT_NAMES()).

As long as there are no more than 16 schedulers, there will never be any lock │ │ │ │ +client_port() -> │ │ │ │ + element(erlang:system_info(scheduler_id) rem tuple_size(?PORT_NAMES()) + 1, │ │ │ │ + ?PORT_NAMES()).

As long as there are no more than 16 schedulers, there will never be any lock │ │ │ │ contention on the port lock for the driver.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Avoiding Copying Binaries When Calling a Driver │ │ │ │

│ │ │ │

There are basically two ways to avoid copying a binary that is sent to a driver:

  • If the Data argument for port_control/3 is a │ │ │ ├── OEBPS/documentation.xhtml │ │ │ │ @@ -17,23 +17,23 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │

    │ │ │ │ Documentation │ │ │ │

    │ │ │ │

    Documentation in Erlang is done through the -moduledoc and -doc │ │ │ │ -attributes. For example:

    -module(arith).
    │ │ │ │ +attributes. For example:

    -module(arith).
    │ │ │ │  -moduledoc """
    │ │ │ │  A module for basic arithmetic.
    │ │ │ │  """.
    │ │ │ │  
    │ │ │ │ --export([add/2]).
    │ │ │ │ +-export([add/2]).
    │ │ │ │  
    │ │ │ │  -doc "Adds two numbers.".
    │ │ │ │ -add(One, Two) -> One + Two.

    The -moduledoc attribute has to be located before the first -doc attribute │ │ │ │ +add(One, Two) -> One + Two.

    The -moduledoc attribute has to be located before the first -doc attribute │ │ │ │ or function declaration. It documents the overall purpose of the module.

    The -doc attribute always precedes the function or │ │ │ │ attribute it documents. The │ │ │ │ attributes that can be documented are │ │ │ │ user-defined types │ │ │ │ (-type and -opaque) and │ │ │ │ behaviour module attributes │ │ │ │ (-callback).

    By default the format used for documentation attributes is │ │ │ │ @@ -45,55 +45,55 @@ │ │ │ │ Documentation Attributes.

    -doc attributes have been available since Erlang/OTP 27.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Documentation metadata │ │ │ │

    │ │ │ │

    It is possible to add metadata to the documentation entry. You do this by adding │ │ │ │ -a -moduledoc or -doc attribute with a map as argument. For example:

    -module(arith).
    │ │ │ │ +a -moduledoc or -doc attribute with a map as argument. For example:

    -module(arith).
    │ │ │ │  -moduledoc """
    │ │ │ │  A module for basic arithmetic.
    │ │ │ │  """.
    │ │ │ │ --moduledoc #{since => "1.0"}.
    │ │ │ │ +-moduledoc #{since => "1.0"}.
    │ │ │ │  
    │ │ │ │ --export([add/2]).
    │ │ │ │ +-export([add/2]).
    │ │ │ │  
    │ │ │ │  -doc "Adds two numbers.".
    │ │ │ │ --doc(#{since => "1.0"}).
    │ │ │ │ -add(One, Two) -> One + Two.

    The metadata is used by documentation tools to provide extra information to the │ │ │ │ +-doc(#{since => "1.0"}). │ │ │ │ +add(One, Two) -> One + Two.

    The metadata is used by documentation tools to provide extra information to the │ │ │ │ user. There can be multiple metadata documentation entries, in which case the │ │ │ │ maps will be merged with the latest taking precedence if there are duplicate │ │ │ │ keys. Example:

    -doc "Adds two numbers.".
    │ │ │ │ --doc #{since => "1.0", author => "Joe"}.
    │ │ │ │ --doc #{since => "2.0"}.
    │ │ │ │ -add(One, Two) -> One + Two.

    This will result in a metadata entry of #{since => "2.0", author => "Joe"}.

    The keys and values in the metadata map can be any type, but it is recommended │ │ │ │ +-doc #{since => "1.0", author => "Joe"}. │ │ │ │ +-doc #{since => "2.0"}. │ │ │ │ +add(One, Two) -> One + Two.

This will result in a metadata entry of #{since => "2.0", author => "Joe"}.

The keys and values in the metadata map can be any type, but it is recommended │ │ │ │ that only atoms are used for keys and │ │ │ │ strings for the values.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ External documentation files │ │ │ │

│ │ │ │

The -moduledoc and -doc can also be placed in external files. To do so use │ │ │ │ -doc {file, "path/to/doc.md"} to point to the documentation. The path used is │ │ │ │ relative to the file where the -doc attribute is located. For example:

%% doc/add.md
│ │ │ │  Adds two numbers.

and

%% src/arith.erl
│ │ │ │ --doc({file, "../doc/add.md"}).
│ │ │ │ -add(One, Two) -> One + Two.

│ │ │ │ +-doc({file, "../doc/add.md"}). │ │ │ │ +add(One, Two) -> One + Two.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Documenting a module │ │ │ │

│ │ │ │

The module description should include details on how to use the API and examples │ │ │ │ of the different functions working together. Here is a good place to use images │ │ │ │ and other diagrams to better show the usage of the module. Instead of writing a │ │ │ │ long text in the moduledoc attribute, it could be better to break it out into │ │ │ │ an external page.

The moduledoc attribute should start with a short paragraph describing the │ │ │ │ -module and then go into greater details. For example:

-module(arith).
│ │ │ │ +module and then go into greater details. For example:

-module(arith).
│ │ │ │  -moduledoc """
│ │ │ │     A module for basic arithmetic.
│ │ │ │  
│ │ │ │     This module can be used to add and subtract values. For example:
│ │ │ │  
│ │ │ │     ```erlang
│ │ │ │     1> arith:substract(arith:add(2, 3), 1).
│ │ │ │ @@ -108,96 +108,96 @@
│ │ │ │  

There are three reserved metadata keys for -moduledoc:

  • since => unicode:chardata() - Shows in which version of the application the module was added. │ │ │ │ If this is added, all functions, types, and callbacks within will also receive │ │ │ │ the same since value unless specified in the metadata of the function, type │ │ │ │ or callback.
  • deprecated => unicode:chardata() - Shows a text in the documentation explaining that it is │ │ │ │ deprecated and what to use instead.
  • format => unicode:chardata() - The format to use for all documentation in this module. The │ │ │ │ default is text/markdown. It should be written using the │ │ │ │ mime type │ │ │ │ -of the format.

Example:

-moduledoc {file, "../doc/arith.asciidoc"}.
│ │ │ │ --moduledoc #{since => "0.1", format => "text/asciidoc"}.
│ │ │ │ --moduledoc #{deprecated => "Use the Erlang arithmetic operators instead."}.

│ │ │ │ +of the format.

Example:

-moduledoc {file, "../doc/arith.asciidoc"}.
│ │ │ │ +-moduledoc #{since => "0.1", format => "text/asciidoc"}.
│ │ │ │ +-moduledoc #{deprecated => "Use the Erlang arithmetic operators instead."}.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Documenting functions, user-defined types, and callbacks │ │ │ │

│ │ │ │

Functions, types, and callbacks can be documented using the -doc attribute. │ │ │ │ Each entry should start with a short paragraph describing the purpose of entity, │ │ │ │ and then go into greater detail in needed.

It is not recommended to include images or diagrams in this documentation as it │ │ │ │ is used by IDEs and c:h/1 to show the documentation to the user.

For example:

-doc """
│ │ │ │  A number that can be used by the arith module.
│ │ │ │  
│ │ │ │  We use a special number here so that we know
│ │ │ │  that this number comes from this module.
│ │ │ │  """.
│ │ │ │ --opaque number() :: {arith, erlang:number()}.
│ │ │ │ +-opaque number() :: {arith, erlang:number()}.
│ │ │ │  
│ │ │ │  -doc """
│ │ │ │  Adds two numbers.
│ │ │ │  
│ │ │ │  ### Example:
│ │ │ │  
│ │ │ │  ```
│ │ │ │  1> arith:add(arith:number(1), arith:number(2)). {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 which version of the application the │ │ │ │ module was added.

  • deprecated => unicode:chardata() - Shows a text in the documentation │ │ │ │ explaining that it is deprecated and what to use instead. The compiler will │ │ │ │ automatically insert this key if there is a -deprecated attribute marking a │ │ │ │ function as deprecated.

  • group => unicode:chardata() - A group that the function, type or callback belongs to. │ │ │ │ It allows tooling, such as shell autocompletion and documentation generators, to list all │ │ │ │ entries within the same group together, often using the group name as an indicator.

  • equiv => unicode:chardata() | F/A | F(...) - Notes that this function is equivalent to │ │ │ │ another function in this module. The equivalence can be described using either │ │ │ │ -Func/Arity, Func(Args) or a unicode string. For example:

    -doc #{equiv => add/3}.
    │ │ │ │ -add(One, Two) -> add(One, Two, []).
    │ │ │ │ -add(One, Two, Options) -> ...

    or

    -doc #{equiv => add(One, Two, [])}.
    │ │ │ │ --spec add(One :: number(), Two :: number()) -> number().
    │ │ │ │ -add(One, Two) -> add(One, Two, []).
    │ │ │ │ -add(One, Two, Options) -> ...

    The entry into the EEP-48 doc chunk metadata is │ │ │ │ +Func/Arity, Func(Args) or a unicode string. For example:

    -doc #{equiv => add/3}.
    │ │ │ │ +add(One, Two) -> add(One, Two, []).
    │ │ │ │ +add(One, Two, Options) -> ...

    or

    -doc #{equiv => add(One, Two, [])}.
    │ │ │ │ +-spec add(One :: number(), Two :: number()) -> number().
    │ │ │ │ +add(One, Two) -> add(One, Two, []).
    │ │ │ │ +add(One, Two, Options) -> ...

    The entry into the EEP-48 doc chunk metadata is │ │ │ │ the value converted to a string.

  • exported => boolean() - A boolean/0 signifying if the entry is exported │ │ │ │ or not. This value is automatically set by the compiler and should not be set │ │ │ │ by the user.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Doc signatures │ │ │ │

│ │ │ │

The doc signature is a short text shown to describe the function and its arguments. │ │ │ │ By default it is determined by looking at the names of the arguments in the │ │ │ │ --spec or function. For example:

add(One, Two) -> One + Two.
│ │ │ │ +-spec or function. For example:

add(One, Two) -> One + Two.
│ │ │ │  
│ │ │ │ --spec sub(One :: integer(), Two :: integer()) -> integer().
│ │ │ │ -sub(X, Y) -> X - Y.

will have a signature of add(One, Two) and sub(One, Two).

For types or callbacks, the signature is derived from the type or callback │ │ │ │ -specification. For example:

-type number(Value) :: {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.

Will create the signature add(One, Two). The signature will be removed from the │ │ │ │ +add(A, B) -> A + B.

Will create the signature add(One, Two). The signature will be removed from the │ │ │ │ documentation string, so in the example above only the text "Adds two numbers" │ │ │ │ will be part of the documentation. This works for functions, types, and │ │ │ │ callbacks.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Compiling and getting documentation │ │ │ │ @@ -282,21 +282,21 @@ │ │ │ │ Using ExDoc to generate HTML/ePub documentation │ │ │ │

│ │ │ │

ExDoc has built-in support to generate │ │ │ │ documentation from Markdown. The simplest way is by using the │ │ │ │ rebar3_ex_doc plugin. To set up a │ │ │ │ rebar3 project to use ExDoc to generate │ │ │ │ documentation add the following to your rebar3.config.

%% Enable the plugin
│ │ │ │ -{plugins, [rebar3_ex_doc]}.
│ │ │ │ +{plugins, [rebar3_ex_doc]}.
│ │ │ │  
│ │ │ │ -{ex_doc, [
│ │ │ │ -  {extras, ["README.md"]},
│ │ │ │ -  {main, "README.md"},
│ │ │ │ -  {source_url, "https://github.com/namespace/your_app"}
│ │ │ │ -]}.

When configured you can run rebar3 ex_doc to generate the │ │ │ │ +{ex_doc, [ │ │ │ │ + {extras, ["README.md"]}, │ │ │ │ + {main, "README.md"}, │ │ │ │ + {source_url, "https://github.com/namespace/your_app"} │ │ │ │ +]}.

When configured you can run rebar3 ex_doc to generate the │ │ │ │ documentation to doc/index.html. For more details and options see │ │ │ │ the rebar3_ex_doc documentation.

You can also download the │ │ │ │ release escript bundle from │ │ │ │ github and run it from the command line. The documentation for using the escript │ │ │ │ is found by running ex_doc --help.

If you are writing documentation that will be using │ │ │ │ ExDoc to generate HTML/ePub it is highly │ │ │ │ recommended to read its documentation.

│ │ │ ├── OEBPS/distributed_applications.xhtml │ │ │ │ @@ -55,36 +55,36 @@ │ │ │ │ (within the time-out specified by sync_nodes_timeout).
  • sync_nodes_timeout = integer() | infinity - Specifies how many milliseconds │ │ │ │ to wait for the other nodes to start.

  • When started, the node waits for all nodes specified by sync_nodes_mandatory │ │ │ │ and sync_nodes_optional to come up. When all nodes are up, or when all │ │ │ │ mandatory nodes are up and the time specified by sync_nodes_timeout has │ │ │ │ elapsed, all applications start. If not all mandatory nodes are up, the node │ │ │ │ terminates.

    Example:

    An application myapp is to run at the node cp1@cave. If this node goes down, │ │ │ │ myapp is to be restarted at cp2@cave or cp3@cave. A system configuration │ │ │ │ -file cp1.config for cp1@cave can look as follows:

    [{kernel,
    │ │ │ │ -  [{distributed, [{myapp, 5000, [cp1@cave, {cp2@cave, cp3@cave}]}]},
    │ │ │ │ -   {sync_nodes_mandatory, [cp2@cave, cp3@cave]},
    │ │ │ │ -   {sync_nodes_timeout, 5000}
    │ │ │ │ -  ]
    │ │ │ │ - }
    │ │ │ │ -].

    The system configuration files for cp2@cave and cp3@cave are identical, │ │ │ │ +file cp1.config for cp1@cave can look as follows:

    [{kernel,
    │ │ │ │ +  [{distributed, [{myapp, 5000, [cp1@cave, {cp2@cave, cp3@cave}]}]},
    │ │ │ │ +   {sync_nodes_mandatory, [cp2@cave, cp3@cave]},
    │ │ │ │ +   {sync_nodes_timeout, 5000}
    │ │ │ │ +  ]
    │ │ │ │ + }
    │ │ │ │ +].

    The system configuration files for cp2@cave and cp3@cave are identical, │ │ │ │ except for the list of mandatory nodes, which is to be [cp1@cave, cp3@cave] │ │ │ │ for cp2@cave and [cp1@cave, cp2@cave] for cp3@cave.

    Note

    All involved nodes must have the same value for distributed and │ │ │ │ sync_nodes_timeout. Otherwise the system behavior is undefined.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Starting and Stopping Distributed Applications │ │ │ │

    │ │ │ │

    When all involved (mandatory) nodes have been started, the distributed │ │ │ │ application can be started by calling application:start(Application) at all │ │ │ │ of these nodes.

    A boot script (see Releases) can be used that │ │ │ │ automatically starts the application.

    The application is started at the first operational node that is listed in the │ │ │ │ list of nodes in the distributed configuration parameter. The application is │ │ │ │ started as usual. That is, an application master is created and calls the │ │ │ │ -application callback function:

    Module:start(normal, StartArgs)

    Example:

    Continuing the example from the previous section, the three nodes are started, │ │ │ │ +application callback function:

    Module:start(normal, StartArgs)

    Example:

    Continuing the example from the previous section, the three nodes are started, │ │ │ │ specifying the system configuration file:

    > erl -sname cp1 -config cp1
    │ │ │ │  > erl -sname cp2 -config cp2
    │ │ │ │  > erl -sname cp3 -config cp3

    When all nodes are operational, myapp can be started. This is achieved by │ │ │ │ calling application:start(myapp) at all three nodes. It is then started at │ │ │ │ cp1, as shown in the following figure:

    Application myapp - Situation 1

    Similarly, the application must be stopped by calling │ │ │ │ application:stop(Application) at all involved nodes.

    │ │ │ │ │ │ │ │ @@ -92,30 +92,30 @@ │ │ │ │ │ │ │ │ Failover │ │ │ │

    │ │ │ │

    If the node where the application is running goes down, the application is │ │ │ │ restarted (after the specified time-out) at the first operational node that is │ │ │ │ listed in the list of nodes in the distributed configuration parameter. This │ │ │ │ is called a failover.

    The application is started the normal way at the new node, that is, by the │ │ │ │ -application master calling:

    Module:start(normal, StartArgs)

    An exception is if the application has the start_phases key defined (see │ │ │ │ +application master calling:

    Module:start(normal, StartArgs)

    An exception is if the application has the start_phases key defined (see │ │ │ │ Included Applications). The application is then │ │ │ │ -instead started by calling:

    Module:start({failover, Node}, StartArgs)

    Here Node is the terminated node.

    Example:

    If cp1 goes down, the system checks which one of the other nodes, cp2 or │ │ │ │ +instead started by calling:

    Module:start({failover, Node}, StartArgs)

    Here Node is the terminated node.

    Example:

    If cp1 goes down, the system checks which one of the other nodes, cp2 or │ │ │ │ cp3, has the least number of running applications, but waits for 5 seconds for │ │ │ │ cp1 to restart. If cp1 does not restart and cp2 runs fewer applications │ │ │ │ than cp3, myapp is restarted on cp2.

    Application myapp - Situation 2

    Suppose now that cp2 goes also down and does not restart within 5 seconds. │ │ │ │ myapp is now restarted on cp3.

    Application myapp - Situation 3

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Takeover │ │ │ │

    │ │ │ │

    If a node is started, which has higher priority according to distributed than │ │ │ │ the node where a distributed application is running, the application is │ │ │ │ restarted at the new node and stopped at the old node. This is called a │ │ │ │ -takeover.

    The application is started by the application master calling:

    Module:start({takeover, Node}, StartArgs)

    Here Node is the old node.

    Example:

    If myapp is running at cp3, and if cp2 now restarts, it does not restart │ │ │ │ +takeover.

    The application is started by the application master calling:

    Module:start({takeover, Node}, StartArgs)

    Here Node is the old node.

    Example:

    If myapp is running at cp3, and if cp2 now restarts, it does not restart │ │ │ │ myapp, as the order between the cp2 and cp3 nodes is undefined.

    Application myapp - Situation 4

    However, if cp1 also restarts, the function application:takeover/2 moves │ │ │ │ myapp to cp1, as cp1 has a higher priority than cp3 for this │ │ │ │ application. In this case, Module:start({takeover, cp3@cave}, StartArgs) is │ │ │ │ executed at cp1 to start the application.

    Application myapp - Situation 5

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/distributed.xhtml │ │ │ │ @@ -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 in runtime by calling net_kernel:start/1.

    Example:

    % erl
    │ │ │ │ -1> node().
    │ │ │ │ +1> node().
    │ │ │ │  nonode@nohost
    │ │ │ │ -2> net_kernel:start([dilbert,shortnames]).
    │ │ │ │ -{ok,<0.102.0>}
    │ │ │ │ -(dilbert@uab)3> node().
    │ │ │ │ +2> net_kernel:start([dilbert,shortnames]).
    │ │ │ │ +{ok,<0.102.0>}
    │ │ │ │ +(dilbert@uab)3> node().
    │ │ │ │  dilbert@uab

    Note

    A node with a long node name cannot communicate with a node with a short node │ │ │ │ name.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Node Connections │ │ │ │

    │ │ │ ├── OEBPS/design_principles.xhtml │ │ │ │ @@ -57,135 +57,135 @@ │ │ │ │ the code for a process in a generic part (a behaviour module) and a specific │ │ │ │ part (a callback module).

    The behaviour module is part of Erlang/OTP. To implement a process such as a │ │ │ │ supervisor, the user only needs to implement the callback module, which is to │ │ │ │ export a pre-defined set of functions, the callback functions.

    The following example illustrate how code can be divided into a generic and a │ │ │ │ specific part. Consider the following code (written in plain Erlang) for a │ │ │ │ simple server, which keeps track of a number of "channels". Other processes can │ │ │ │ allocate and free the channels by calling the functions alloc/0 and free/1, │ │ │ │ -respectively.

    -module(ch1).
    │ │ │ │ --export([start/0]).
    │ │ │ │ --export([alloc/0, free/1]).
    │ │ │ │ --export([init/0]).
    │ │ │ │ +respectively.

    -module(ch1).
    │ │ │ │ +-export([start/0]).
    │ │ │ │ +-export([alloc/0, free/1]).
    │ │ │ │ +-export([init/0]).
    │ │ │ │  
    │ │ │ │ -start() ->
    │ │ │ │ -    spawn(ch1, init, []).
    │ │ │ │ +start() ->
    │ │ │ │ +    spawn(ch1, init, []).
    │ │ │ │  
    │ │ │ │ -alloc() ->
    │ │ │ │ -    ch1 ! {self(), alloc},
    │ │ │ │ +alloc() ->
    │ │ │ │ +    ch1 ! {self(), alloc},
    │ │ │ │      receive
    │ │ │ │ -        {ch1, Res} ->
    │ │ │ │ +        {ch1, Res} ->
    │ │ │ │              Res
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -free(Ch) ->
    │ │ │ │ -    ch1 ! {free, Ch},
    │ │ │ │ +free(Ch) ->
    │ │ │ │ +    ch1 ! {free, Ch},
    │ │ │ │      ok.
    │ │ │ │  
    │ │ │ │ -init() ->
    │ │ │ │ -    register(ch1, self()),
    │ │ │ │ -    Chs = channels(),
    │ │ │ │ -    loop(Chs).
    │ │ │ │ +init() ->
    │ │ │ │ +    register(ch1, self()),
    │ │ │ │ +    Chs = channels(),
    │ │ │ │ +    loop(Chs).
    │ │ │ │  
    │ │ │ │ -loop(Chs) ->
    │ │ │ │ +loop(Chs) ->
    │ │ │ │      receive
    │ │ │ │ -        {From, alloc} ->
    │ │ │ │ -            {Ch, Chs2} = alloc(Chs),
    │ │ │ │ -            From ! {ch1, Ch},
    │ │ │ │ -            loop(Chs2);
    │ │ │ │ -        {free, Ch} ->
    │ │ │ │ -            Chs2 = free(Ch, Chs),
    │ │ │ │ -            loop(Chs2)
    │ │ │ │ -    end.

    The code for the server can be rewritten into a generic part server.erl:

    -module(server).
    │ │ │ │ --export([start/1]).
    │ │ │ │ --export([call/2, cast/2]).
    │ │ │ │ --export([init/1]).
    │ │ │ │ +        {From, alloc} ->
    │ │ │ │ +            {Ch, Chs2} = alloc(Chs),
    │ │ │ │ +            From ! {ch1, Ch},
    │ │ │ │ +            loop(Chs2);
    │ │ │ │ +        {free, Ch} ->
    │ │ │ │ +            Chs2 = free(Ch, Chs),
    │ │ │ │ +            loop(Chs2)
    │ │ │ │ +    end.

    The code for the server can be rewritten into a generic part server.erl:

    -module(server).
    │ │ │ │ +-export([start/1]).
    │ │ │ │ +-export([call/2, cast/2]).
    │ │ │ │ +-export([init/1]).
    │ │ │ │  
    │ │ │ │ -start(Mod) ->
    │ │ │ │ -    spawn(server, init, [Mod]).
    │ │ │ │ +start(Mod) ->
    │ │ │ │ +    spawn(server, init, [Mod]).
    │ │ │ │  
    │ │ │ │ -call(Name, Req) ->
    │ │ │ │ -    Name ! {call, self(), Req},
    │ │ │ │ +call(Name, Req) ->
    │ │ │ │ +    Name ! {call, self(), Req},
    │ │ │ │      receive
    │ │ │ │ -        {Name, Res} ->
    │ │ │ │ +        {Name, Res} ->
    │ │ │ │              Res
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -cast(Name, Req) ->
    │ │ │ │ -    Name ! {cast, Req},
    │ │ │ │ +cast(Name, Req) ->
    │ │ │ │ +    Name ! {cast, Req},
    │ │ │ │      ok.
    │ │ │ │  
    │ │ │ │ -init(Mod) ->
    │ │ │ │ -    register(Mod, self()),
    │ │ │ │ -    State = Mod:init(),
    │ │ │ │ -    loop(Mod, State).
    │ │ │ │ +init(Mod) ->
    │ │ │ │ +    register(Mod, self()),
    │ │ │ │ +    State = Mod:init(),
    │ │ │ │ +    loop(Mod, State).
    │ │ │ │  
    │ │ │ │ -loop(Mod, State) ->
    │ │ │ │ +loop(Mod, State) ->
    │ │ │ │      receive
    │ │ │ │ -        {call, From, Req} ->
    │ │ │ │ -            {Res, State2} = Mod:handle_call(Req, State),
    │ │ │ │ -            From ! {Mod, Res},
    │ │ │ │ -            loop(Mod, State2);
    │ │ │ │ -        {cast, Req} ->
    │ │ │ │ -            State2 = Mod:handle_cast(Req, State),
    │ │ │ │ -            loop(Mod, State2)
    │ │ │ │ -    end.

    And a callback module ch2.erl:

    -module(ch2).
    │ │ │ │ --export([start/0]).
    │ │ │ │ --export([alloc/0, free/1]).
    │ │ │ │ --export([init/0, handle_call/2, handle_cast/2]).
    │ │ │ │ -
    │ │ │ │ -start() ->
    │ │ │ │ -    server:start(ch2).
    │ │ │ │ -
    │ │ │ │ -alloc() ->
    │ │ │ │ -    server:call(ch2, alloc).
    │ │ │ │ -
    │ │ │ │ -free(Ch) ->
    │ │ │ │ -    server:cast(ch2, {free, Ch}).
    │ │ │ │ +        {call, From, Req} ->
    │ │ │ │ +            {Res, State2} = Mod:handle_call(Req, State),
    │ │ │ │ +            From ! {Mod, Res},
    │ │ │ │ +            loop(Mod, State2);
    │ │ │ │ +        {cast, Req} ->
    │ │ │ │ +            State2 = Mod:handle_cast(Req, State),
    │ │ │ │ +            loop(Mod, State2)
    │ │ │ │ +    end.

    And a callback module ch2.erl:

    -module(ch2).
    │ │ │ │ +-export([start/0]).
    │ │ │ │ +-export([alloc/0, free/1]).
    │ │ │ │ +-export([init/0, handle_call/2, handle_cast/2]).
    │ │ │ │ +
    │ │ │ │ +start() ->
    │ │ │ │ +    server:start(ch2).
    │ │ │ │ +
    │ │ │ │ +alloc() ->
    │ │ │ │ +    server:call(ch2, alloc).
    │ │ │ │ +
    │ │ │ │ +free(Ch) ->
    │ │ │ │ +    server:cast(ch2, {free, Ch}).
    │ │ │ │  
    │ │ │ │ -init() ->
    │ │ │ │ -    channels().
    │ │ │ │ +init() ->
    │ │ │ │ +    channels().
    │ │ │ │  
    │ │ │ │ -handle_call(alloc, Chs) ->
    │ │ │ │ -    alloc(Chs). % => {Ch,Chs2}
    │ │ │ │ +handle_call(alloc, Chs) ->
    │ │ │ │ +    alloc(Chs). % => {Ch,Chs2}
    │ │ │ │  
    │ │ │ │ -handle_cast({free, Ch}, Chs) ->
    │ │ │ │ -    free(Ch, Chs). % => Chs2

    Notice the following:

    • The code in server can be reused to build many different servers.
    • The server name, in this example the atom ch2, is hidden from the users of │ │ │ │ +handle_cast({free, Ch}, Chs) -> │ │ │ │ + free(Ch, Chs). % => Chs2

    Notice the following:

    • The code in server can be reused to build many different servers.
    • The server name, in this example the atom ch2, is hidden from the users of │ │ │ │ the client functions. This means that the name can be changed without │ │ │ │ affecting them.
    • The protocol (messages sent to and received from the server) is also hidden. │ │ │ │ This is good programming practice and allows one to change the protocol │ │ │ │ without changing the code using the interface functions.
    • The functionality of server can be extended without having to change ch2 │ │ │ │ or any other callback module.

    In ch1.erl and ch2.erl above, the implementation of channels/0, alloc/1, │ │ │ │ and free/2 has been intentionally left out, as it is not relevant to the │ │ │ │ example. For completeness, one way to write these functions is given below. This │ │ │ │ is an example only, a realistic implementation must be able to handle situations │ │ │ │ -like running out of channels to allocate, and so on.

    channels() ->
    │ │ │ │ -   {_Allocated = [], _Free = lists:seq(1, 100)}.
    │ │ │ │ +like running out of channels to allocate, and so on.

    channels() ->
    │ │ │ │ +   {_Allocated = [], _Free = lists:seq(1, 100)}.
    │ │ │ │  
    │ │ │ │ -alloc({Allocated, [H|T] = _Free}) ->
    │ │ │ │ -   {H, {[H|Allocated], T}}.
    │ │ │ │ +alloc({Allocated, [H|T] = _Free}) ->
    │ │ │ │ +   {H, {[H|Allocated], T}}.
    │ │ │ │  
    │ │ │ │ -free(Ch, {Alloc, Free} = Channels) ->
    │ │ │ │ -   case lists:member(Ch, Alloc) of
    │ │ │ │ +free(Ch, {Alloc, Free} = Channels) ->
    │ │ │ │ +   case lists:member(Ch, Alloc) of
    │ │ │ │        true ->
    │ │ │ │ -         {lists:delete(Ch, Alloc), [Ch|Free]};
    │ │ │ │ +         {lists:delete(Ch, Alloc), [Ch|Free]};
    │ │ │ │        false ->
    │ │ │ │           Channels
    │ │ │ │     end.

    Code written without using behaviours can be more efficient, but the increased │ │ │ │ efficiency is at the expense of generality. The ability to manage all │ │ │ │ applications in the system in a consistent manner is important.

    Using behaviours also makes it easier to read and understand code written by │ │ │ │ other programmers. Improvised programming structures, while possibly more │ │ │ │ efficient, are always more difficult to understand.

    The server module corresponds, greatly simplified, to the Erlang/OTP behaviour │ │ │ │ gen_server.

    The standard Erlang/OTP behaviours are:

    • gen_server

      For implementing the server of a client-server relation

    • gen_statem

      For implementing state machines

    • gen_event

      For implementing event handling functionality

    • supervisor

      For implementing a supervisor in a supervision tree

    The compiler understands the module attribute -behaviour(Behaviour) and issues │ │ │ │ -warnings about missing callback functions, for example:

    -module(chs3).
    │ │ │ │ --behaviour(gen_server).
    │ │ │ │ +warnings about missing callback functions, for example:

    -module(chs3).
    │ │ │ │ +-behaviour(gen_server).
    │ │ │ │  ...
    │ │ │ │  
    │ │ │ │ -3> c(chs3).
    │ │ │ │ +3> c(chs3).
    │ │ │ │  ./chs3.erl:10: Warning: undefined call-back function handle_call/3
    │ │ │ │ -{ok,chs3}

    │ │ │ │ +{ok,chs3}

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Applications │ │ │ │

    │ │ │ │

    Erlang/OTP comes with a number of components, each implementing some specific │ │ │ │ functionality. Components are with Erlang/OTP terminology called applications. │ │ │ ├── OEBPS/data_types.xhtml │ │ │ │ @@ -104,18 +104,18 @@ │ │ │ │ │ │ │ │ Representation of Floating Point Numbers │ │ │ │ │ │ │ │

    When working with floats you may not see what you expect when printing or doing │ │ │ │ arithmetic operations. This is because floats are represented by a fixed number │ │ │ │ of bits in a base-2 system while printed floats are represented with a base-10 │ │ │ │ system. Erlang uses 64-bit floats. Here are examples of this phenomenon:

    1> 0.1+0.2.
    │ │ │ │ -0.30000000000000004

    The real numbers 0.1 and 0.2 cannot be represented exactly as floats.

    1> {36028797018963968.0, 36028797018963968 == 36028797018963968.0,
    │ │ │ │ -  36028797018963970.0, 36028797018963970 == 36028797018963970.0}.
    │ │ │ │ -{3.602879701896397e16, true,
    │ │ │ │ - 3.602879701896397e16, false}.

    The value 36028797018963968 can be represented exactly as a float value but │ │ │ │ +0.30000000000000004

    The real numbers 0.1 and 0.2 cannot be represented exactly as floats.

    1> {36028797018963968.0, 36028797018963968 == 36028797018963968.0,
    │ │ │ │ +  36028797018963970.0, 36028797018963970 == 36028797018963970.0}.
    │ │ │ │ +{3.602879701896397e16, true,
    │ │ │ │ + 3.602879701896397e16, false}.

    The value 36028797018963968 can be represented exactly as a float value but │ │ │ │ Erlang's pretty printer rounds 36028797018963968.0 to 3.602879701896397e16 │ │ │ │ (=36028797018963970.0) as all values in the range │ │ │ │ [36028797018963966.0, 36028797018963972.0] are represented by │ │ │ │ 36028797018963968.0.

    For more information about floats and issues with them see:

    If you need to work with exact decimal fractions, for instance to represent │ │ │ │ money, it is recommended to use a library that handles that, or work in │ │ │ │ cents instead of dollars or euros so that decimal fractions are not needed.

    Also note that Erlang's floats do not exactly match IEEE 754 floats, │ │ │ │ in that neither Inf nor NaN are supported in Erlang. Any │ │ │ │ @@ -149,52 +149,52 @@ │ │ │ │ by eight are called binaries.

    Examples:

    1> <<10,20>>.
    │ │ │ │  <<10,20>>
    │ │ │ │  2> <<"ABC">>.
    │ │ │ │  <<"ABC">>
    │ │ │ │  3> <<1:1,0:1>>.
    │ │ │ │  <<2:2>>

    The is_bitstring/1 BIF tests whether a │ │ │ │ term is a bit string, and the is_binary/1 │ │ │ │ -BIF tests whether a term is a binary.

    Examples:

    1> is_bitstring(<<1:1>>).
    │ │ │ │ +BIF tests whether a term is a binary.

    Examples:

    1> is_bitstring(<<1:1>>).
    │ │ │ │  true
    │ │ │ │ -2> is_binary(<<1:1>>).
    │ │ │ │ +2> is_binary(<<1:1>>).
    │ │ │ │  false
    │ │ │ │ -3> is_binary(<<42>>).
    │ │ │ │ +3> is_binary(<<42>>).
    │ │ │ │  true
    │ │ │ │  

    For more examples, see Programming Examples.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Reference │ │ │ │

    │ │ │ │

    A term that is unique │ │ │ │ among connected nodes. A reference is created by calling the │ │ │ │ make_ref/0 BIF. The │ │ │ │ is_reference/1 BIF tests whether a term │ │ │ │ -is a reference.

    Examples:

    1> Ref = make_ref().
    │ │ │ │ +is a reference.

    Examples:

    1> Ref = make_ref().
    │ │ │ │  #Ref<0.76482849.3801088007.198204>
    │ │ │ │ -2> is_reference(Ref).
    │ │ │ │ +2> is_reference(Ref).
    │ │ │ │  true

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Fun │ │ │ │

    │ │ │ │

    A fun is a functional object. Funs make it possible to create an anonymous │ │ │ │ function and pass the function itself — not its name — as argument to other │ │ │ │ -functions.

    Examples:

    1> Fun1 = fun (X) -> X+1 end.
    │ │ │ │ +functions.

    Examples:

    1> Fun1 = fun (X) -> X+1 end.
    │ │ │ │  #Fun<erl_eval.6.39074546>
    │ │ │ │ -2> Fun1(2).
    │ │ │ │ +2> Fun1(2).
    │ │ │ │  3

    The is_function/1 and is_function/2 │ │ │ │ -BIFs tests whether a term is a fun.

    Examples:

    1> F = fun() -> ok end.
    │ │ │ │ +BIFs tests whether a term is a fun.

    Examples:

    1> F = fun() -> ok end.
    │ │ │ │  #Fun<erl_eval.43.105768164>
    │ │ │ │ -2> is_function(F).
    │ │ │ │ +2> is_function(F).
    │ │ │ │  true
    │ │ │ │ -3> is_function(F, 0).
    │ │ │ │ +3> is_function(F, 0).
    │ │ │ │  true
    │ │ │ │ -4> is_function(F, 1).
    │ │ │ │ +4> is_function(F, 1).
    │ │ │ │  false

    Read more about funs in Fun Expressions. For more │ │ │ │ examples, see Programming Examples.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Port Identifier │ │ │ │

    │ │ │ │ @@ -212,94 +212,94 @@ │ │ │ │ for a new process after a while.

    The BIF self/0 returns the Pid of the calling process. When │ │ │ │ creating a new process, the parent │ │ │ │ process will be able to get the Pid of the child process either via the return │ │ │ │ value, as is the case when calling the spawn/3 BIF, or via │ │ │ │ a message, which is the case when calling the │ │ │ │ spawn_request/5 BIF. A Pid is typically used when │ │ │ │ when sending a process a signal. The │ │ │ │ -is_pid/1 BIF tests whether a term is a Pid.

    Example:

    -module(m).
    │ │ │ │ --export([loop/0]).
    │ │ │ │ +is_pid/1 BIF tests whether a term is a Pid.

    Example:

    -module(m).
    │ │ │ │ +-export([loop/0]).
    │ │ │ │  
    │ │ │ │ -loop() ->
    │ │ │ │ +loop() ->
    │ │ │ │      receive
    │ │ │ │          who_are_you ->
    │ │ │ │ -            io:format("I am ~p~n", [self()]),
    │ │ │ │ -            loop()
    │ │ │ │ +            io:format("I am ~p~n", [self()]),
    │ │ │ │ +            loop()
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -1> P = spawn(m, loop, []).
    │ │ │ │ +1> P = spawn(m, loop, []).
    │ │ │ │  <0.58.0>
    │ │ │ │  2> P ! who_are_you.
    │ │ │ │  I am <0.58.0>
    │ │ │ │  who_are_you

    Read more about processes in Processes.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Tuple │ │ │ │

    │ │ │ │

    A tuple is a compound data type with a fixed number of terms:

    {Term1,...,TermN}

    Each term Term in the tuple is called an element. The number of elements is │ │ │ │ -said to be the size of the tuple.

    There exists a number of BIFs to manipulate tuples.

    Examples:

    1> P = {adam,24,{july,29}}.
    │ │ │ │ -{adam,24,{july,29}}
    │ │ │ │ -2> element(1,P).
    │ │ │ │ +said to be the size of the tuple.

    There exists a number of BIFs to manipulate tuples.

    Examples:

    1> P = {adam,24,{july,29}}.
    │ │ │ │ +{adam,24,{july,29}}
    │ │ │ │ +2> element(1,P).
    │ │ │ │  adam
    │ │ │ │ -3> element(3,P).
    │ │ │ │ -{july,29}
    │ │ │ │ -4> P2 = setelement(2,P,25).
    │ │ │ │ -{adam,25,{july,29}}
    │ │ │ │ -5> tuple_size(P).
    │ │ │ │ +3> element(3,P).
    │ │ │ │ +{july,29}
    │ │ │ │ +4> P2 = setelement(2,P,25).
    │ │ │ │ +{adam,25,{july,29}}
    │ │ │ │ +5> tuple_size(P).
    │ │ │ │  3
    │ │ │ │ -6> tuple_size({}).
    │ │ │ │ +6> tuple_size({}).
    │ │ │ │  0
    │ │ │ │ -7> is_tuple({a,b,c}).
    │ │ │ │ +7> is_tuple({a,b,c}).
    │ │ │ │  true

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Map │ │ │ │

    │ │ │ │

    A map is a compound data type with a variable number of key-value associations:

    #{Key1 => Value1, ..., KeyN => ValueN}

    Each key-value association in the map is called an association pair. The key │ │ │ │ and value parts of the pair are called elements. The number of association │ │ │ │ -pairs is said to be the size of the map.

    There exists a number of BIFs to manipulate maps.

    Examples:

    1> M1 = #{name => adam, age => 24, date => {july,29}}.
    │ │ │ │ -#{age => 24,date => {july,29},name => adam}
    │ │ │ │ -2> maps:get(name, M1).
    │ │ │ │ +pairs is said to be the size of the map.

    There exists a number of BIFs to manipulate maps.

    Examples:

    1> M1 = #{name => adam, age => 24, date => {july,29}}.
    │ │ │ │ +#{age => 24,date => {july,29},name => adam}
    │ │ │ │ +2> maps:get(name, M1).
    │ │ │ │  adam
    │ │ │ │ -3> maps:get(date, M1).
    │ │ │ │ -{july,29}
    │ │ │ │ -4> M2 = maps:update(age, 25, M1).
    │ │ │ │ -#{age => 25,date => {july,29},name => adam}
    │ │ │ │ -5> map_size(M).
    │ │ │ │ +3> maps:get(date, M1).
    │ │ │ │ +{july,29}
    │ │ │ │ +4> M2 = maps:update(age, 25, M1).
    │ │ │ │ +#{age => 25,date => {july,29},name => adam}
    │ │ │ │ +5> map_size(M).
    │ │ │ │  3
    │ │ │ │ -6> map_size(#{}).
    │ │ │ │ +6> map_size(#{}).
    │ │ │ │  0

    A collection of maps processing functions are found in module maps │ │ │ │ in STDLIB.

    Read more about maps in Map Expressions.

    Change

    Maps were introduced as an experimental feature in Erlang/OTP R17. Their │ │ │ │ functionality was extended and became fully supported in Erlang/OTP 18.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ List │ │ │ │

    │ │ │ │

    A list is a compound data type with a variable number of terms.

    [Term1,...,TermN]

    Each term Term in the list is called an element. The number of elements is │ │ │ │ said to be the length of the list.

    Formally, a list is either the empty list [] or consists of a head (first │ │ │ │ element) and a tail (remainder of the list). The tail is also a list. The │ │ │ │ latter can be expressed as [H|T]. The notation [Term1,...,TermN] above is │ │ │ │ equivalent with the list [Term1|[...|[TermN|[]]]].

    Example:

    [] is a list, thus
    [c|[]] is a list, thus
    [b|[c|[]]] is a list, thus
    [a|[b|[c|[]]]] is a list, or in short [a,b,c]

    A list where the tail is a list is sometimes called a proper list. It is │ │ │ │ allowed to have a list where the tail is not a list, for example, [a|b]. │ │ │ │ -However, this type of list is of little practical use.

    Examples:

    1> L1 = [a,2,{c,4}].
    │ │ │ │ -[a,2,{c,4}]
    │ │ │ │ -2> [H|T] = L1.
    │ │ │ │ -[a,2,{c,4}]
    │ │ │ │ +However, this type of list is of little practical use.

    Examples:

    1> L1 = [a,2,{c,4}].
    │ │ │ │ +[a,2,{c,4}]
    │ │ │ │ +2> [H|T] = L1.
    │ │ │ │ +[a,2,{c,4}]
    │ │ │ │  3> H.
    │ │ │ │  a
    │ │ │ │  4> T.
    │ │ │ │ -[2,{c,4}]
    │ │ │ │ -5> L2 = [d|T].
    │ │ │ │ -[d,2,{c,4}]
    │ │ │ │ -6> length(L1).
    │ │ │ │ +[2,{c,4}]
    │ │ │ │ +5> L2 = [d|T].
    │ │ │ │ +[d,2,{c,4}]
    │ │ │ │ +6> length(L1).
    │ │ │ │  3
    │ │ │ │ -7> length([]).
    │ │ │ │ +7> length([]).
    │ │ │ │  0

    A collection of list processing functions are found in module │ │ │ │ lists in STDLIB.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ String │ │ │ │

    │ │ │ │ @@ -419,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 are the │ │ │ │ applications ERTS, Kernel, STDLIB and SASL.

      Step 1. Create the file .rel:

      %% mysystem2.rel
      │ │ │ │ -{release,
      │ │ │ │ - {"MYSYSTEM", "SECOND"},
      │ │ │ │ - {erts, "6.0"},
      │ │ │ │ - [{kernel, "3.0"},
      │ │ │ │ -  {stdlib, "2.0"},
      │ │ │ │ -  {sasl, "2.4"},
      │ │ │ │ -  {pea, "2.0"}]}.

      Step 2. Create the application upgrade file (see │ │ │ │ +{release, │ │ │ │ + {"MYSYSTEM", "SECOND"}, │ │ │ │ + {erts, "6.0"}, │ │ │ │ + [{kernel, "3.0"}, │ │ │ │ + {stdlib, "2.0"}, │ │ │ │ + {sasl, "2.4"}, │ │ │ │ + {pea, "2.0"}]}.

    Step 2. Create the application upgrade file (see │ │ │ │ appup in SASL) for Pea, for example:

    %% pea.appup
    │ │ │ │ -{"2.0",
    │ │ │ │ - [{"1.0",[{load_module,pea_lib}]}],
    │ │ │ │ - [{"1.0",[{load_module,pea_lib}]}]}.

    Step 3. From the directory where the file mysystem2.rel resides, start the │ │ │ │ +{"2.0", │ │ │ │ + [{"1.0",[{load_module,pea_lib}]}], │ │ │ │ + [{"1.0",[{load_module,pea_lib}]}]}.

    Step 3. From the directory where the file mysystem2.rel resides, start the │ │ │ │ Erlang/OTP system, giving the path to the new version of Pea:

    % erl -pa /home/user/target_system/myapps/pea-2.0/ebin

    Step 4. Create the release upgrade file (see relup │ │ │ │ in SASL):

    1> systools:make_relup("mysystem2",["mysystem"],["mysystem"],
    │ │ │ │      [{path,["/home/user/target_system/myapps/pea-1.0/ebin",
    │ │ │ │      "/my/old/erlang/lib/*/ebin"]}]).

    Here "mysystem" is the base release and "mysystem2" is the release to │ │ │ │ upgrade to.

    The path option is used for pointing out the old version of all applications. │ │ │ │ (The new versions are already in the code path - assuming of course that the │ │ │ │ Erlang node on which this is executed is running the correct version of │ │ │ │ @@ -197,21 +197,21 @@ │ │ │ │ {continue_after_restart,"FIRST",[]} │ │ │ │ heart: Tue Apr 1 12:15:10 2014: Erlang has closed. │ │ │ │ heart: Tue Apr 1 12:15:11 2014: Executed "/usr/local/erl-target/bin/start /usr/local/erl-target/releases/new_start_erl.data" -> 0. Terminating. │ │ │ │ [End]

    The above return value and output after the call to │ │ │ │ release_handler:install_release/1 means that the release_handler has │ │ │ │ restarted the node by using heart. This is always done when the upgrade │ │ │ │ involves a change of the applications ERTS, Kernel, STDLIB, or SASL. For more │ │ │ │ -information, see Upgrade when Erlang/OTP has Changed.

    The node is accessible through a new pipe:

    % /usr/local/erl-target/bin/to_erl /tmp/erlang.pipe.2

    List the available releases in the system:

    1> release_handler:which_releases().
    │ │ │ │ -[{"MYSYSTEM","SECOND",
    │ │ │ │ -  ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"],
    │ │ │ │ -  current},
    │ │ │ │ - {"MYSYSTEM","FIRST",
    │ │ │ │ -  ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"],
    │ │ │ │ -  permanent}]

    Our new release, "SECOND", is now the current release, but we can also see that │ │ │ │ +information, see Upgrade when Erlang/OTP has Changed.

    The node is accessible through a new pipe:

    % /usr/local/erl-target/bin/to_erl /tmp/erlang.pipe.2

    List the available releases in the system:

    1> release_handler:which_releases().
    │ │ │ │ +[{"MYSYSTEM","SECOND",
    │ │ │ │ +  ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"],
    │ │ │ │ +  current},
    │ │ │ │ + {"MYSYSTEM","FIRST",
    │ │ │ │ +  ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"],
    │ │ │ │ +  permanent}]

    Our new release, "SECOND", is now the current release, but we can also see that │ │ │ │ our "FIRST" release is still permanent. This means that if the node would be │ │ │ │ restarted now, it would come up running the "FIRST" release again.

    Step 3. Make the new release permanent:

    2> release_handler:make_permanent("SECOND").

    Check the releases again:

    3> release_handler:which_releases().
    │ │ │ │  [{"MYSYSTEM","SECOND",
    │ │ │ │    ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"],
    │ │ │ │    permanent},
    │ │ │ │   {"MYSYSTEM","FIRST",
    │ │ │ │    ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"],
    │ │ │ │ @@ -220,264 +220,264 @@
    │ │ │ │    
    │ │ │ │      
    │ │ │ │    
    │ │ │ │    Listing of target_system.erl
    │ │ │ │  
    │ │ │ │  

    This module can also be found in the examples directory of the SASL │ │ │ │ application.

    
    │ │ │ │ --module(target_system).
    │ │ │ │ --export([create/1, create/2, install/2]).
    │ │ │ │ +-module(target_system).
    │ │ │ │ +-export([create/1, create/2, install/2]).
    │ │ │ │  
    │ │ │ │  %% Note: RelFileName below is the *stem* without trailing .rel,
    │ │ │ │  %% .script etc.
    │ │ │ │  %%
    │ │ │ │  
    │ │ │ │  %% create(RelFileName)
    │ │ │ │  %%
    │ │ │ │ -create(RelFileName) ->
    │ │ │ │ -    create(RelFileName,[]).
    │ │ │ │ +create(RelFileName) ->
    │ │ │ │ +    create(RelFileName,[]).
    │ │ │ │  
    │ │ │ │ -create(RelFileName,SystoolsOpts) ->
    │ │ │ │ +create(RelFileName,SystoolsOpts) ->
    │ │ │ │      RelFile = RelFileName ++ ".rel",
    │ │ │ │ -    Dir = filename:dirname(RelFileName),
    │ │ │ │ -    PlainRelFileName = filename:join(Dir,"plain"),
    │ │ │ │ +    Dir = filename:dirname(RelFileName),
    │ │ │ │ +    PlainRelFileName = filename:join(Dir,"plain"),
    │ │ │ │      PlainRelFile = PlainRelFileName ++ ".rel",
    │ │ │ │ -    io:fwrite("Reading file: ~ts ...~n", [RelFile]),
    │ │ │ │ -    {ok, [RelSpec]} = file:consult(RelFile),
    │ │ │ │ -    io:fwrite("Creating file: ~ts from ~ts ...~n",
    │ │ │ │ -              [PlainRelFile, RelFile]),
    │ │ │ │ -    {release,
    │ │ │ │ -     {RelName, RelVsn},
    │ │ │ │ -     {erts, ErtsVsn},
    │ │ │ │ -     AppVsns} = RelSpec,
    │ │ │ │ -    PlainRelSpec = {release,
    │ │ │ │ -                    {RelName, RelVsn},
    │ │ │ │ -                    {erts, ErtsVsn},
    │ │ │ │ -                    lists:filter(fun({kernel, _}) ->
    │ │ │ │ +    io:fwrite("Reading file: ~ts ...~n", [RelFile]),
    │ │ │ │ +    {ok, [RelSpec]} = file:consult(RelFile),
    │ │ │ │ +    io:fwrite("Creating file: ~ts from ~ts ...~n",
    │ │ │ │ +              [PlainRelFile, RelFile]),
    │ │ │ │ +    {release,
    │ │ │ │ +     {RelName, RelVsn},
    │ │ │ │ +     {erts, ErtsVsn},
    │ │ │ │ +     AppVsns} = RelSpec,
    │ │ │ │ +    PlainRelSpec = {release,
    │ │ │ │ +                    {RelName, RelVsn},
    │ │ │ │ +                    {erts, ErtsVsn},
    │ │ │ │ +                    lists:filter(fun({kernel, _}) ->
    │ │ │ │                                           true;
    │ │ │ │ -                                    ({stdlib, _}) ->
    │ │ │ │ +                                    ({stdlib, _}) ->
    │ │ │ │                                           true;
    │ │ │ │ -                                    (_) ->
    │ │ │ │ +                                    (_) ->
    │ │ │ │                                           false
    │ │ │ │ -                                 end, AppVsns)
    │ │ │ │ -                   },
    │ │ │ │ -    {ok, Fd} = file:open(PlainRelFile, [write]),
    │ │ │ │ -    io:fwrite(Fd, "~p.~n", [PlainRelSpec]),
    │ │ │ │ -    file:close(Fd),
    │ │ │ │ -
    │ │ │ │ -    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
    │ │ │ │ -	      [PlainRelFileName,PlainRelFileName]),
    │ │ │ │ -    make_script(PlainRelFileName,SystoolsOpts),
    │ │ │ │ -
    │ │ │ │ -    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
    │ │ │ │ -              [RelFileName, RelFileName]),
    │ │ │ │ -    make_script(RelFileName,SystoolsOpts),
    │ │ │ │ +                                 end, AppVsns)
    │ │ │ │ +                   },
    │ │ │ │ +    {ok, Fd} = file:open(PlainRelFile, [write]),
    │ │ │ │ +    io:fwrite(Fd, "~p.~n", [PlainRelSpec]),
    │ │ │ │ +    file:close(Fd),
    │ │ │ │ +
    │ │ │ │ +    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
    │ │ │ │ +	      [PlainRelFileName,PlainRelFileName]),
    │ │ │ │ +    make_script(PlainRelFileName,SystoolsOpts),
    │ │ │ │ +
    │ │ │ │ +    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
    │ │ │ │ +              [RelFileName, RelFileName]),
    │ │ │ │ +    make_script(RelFileName,SystoolsOpts),
    │ │ │ │  
    │ │ │ │      TarFileName = RelFileName ++ ".tar.gz",
    │ │ │ │ -    io:fwrite("Creating tar file ~ts ...~n", [TarFileName]),
    │ │ │ │ -    make_tar(RelFileName,SystoolsOpts),
    │ │ │ │ +    io:fwrite("Creating tar file ~ts ...~n", [TarFileName]),
    │ │ │ │ +    make_tar(RelFileName,SystoolsOpts),
    │ │ │ │  
    │ │ │ │ -    TmpDir = filename:join(Dir,"tmp"),
    │ │ │ │ -    io:fwrite("Creating directory ~tp ...~n",[TmpDir]),
    │ │ │ │ -    file:make_dir(TmpDir),
    │ │ │ │ -
    │ │ │ │ -    io:fwrite("Extracting ~ts into directory ~ts ...~n", [TarFileName,TmpDir]),
    │ │ │ │ -    extract_tar(TarFileName, TmpDir),
    │ │ │ │ -
    │ │ │ │ -    TmpBinDir = filename:join([TmpDir, "bin"]),
    │ │ │ │ -    ErtsBinDir = filename:join([TmpDir, "erts-" ++ ErtsVsn, "bin"]),
    │ │ │ │ -    io:fwrite("Deleting \"erl\" and \"start\" in directory ~ts ...~n",
    │ │ │ │ -              [ErtsBinDir]),
    │ │ │ │ -    file:delete(filename:join([ErtsBinDir, "erl"])),
    │ │ │ │ -    file:delete(filename:join([ErtsBinDir, "start"])),
    │ │ │ │ -
    │ │ │ │ -    io:fwrite("Creating temporary directory ~ts ...~n", [TmpBinDir]),
    │ │ │ │ -    file:make_dir(TmpBinDir),
    │ │ │ │ -
    │ │ │ │ -    io:fwrite("Copying file \"~ts.boot\" to ~ts ...~n",
    │ │ │ │ -              [PlainRelFileName, filename:join([TmpBinDir, "start.boot"])]),
    │ │ │ │ -    copy_file(PlainRelFileName++".boot",filename:join([TmpBinDir, "start.boot"])),
    │ │ │ │ +    TmpDir = filename:join(Dir,"tmp"),
    │ │ │ │ +    io:fwrite("Creating directory ~tp ...~n",[TmpDir]),
    │ │ │ │ +    file:make_dir(TmpDir),
    │ │ │ │ +
    │ │ │ │ +    io:fwrite("Extracting ~ts into directory ~ts ...~n", [TarFileName,TmpDir]),
    │ │ │ │ +    extract_tar(TarFileName, TmpDir),
    │ │ │ │ +
    │ │ │ │ +    TmpBinDir = filename:join([TmpDir, "bin"]),
    │ │ │ │ +    ErtsBinDir = filename:join([TmpDir, "erts-" ++ ErtsVsn, "bin"]),
    │ │ │ │ +    io:fwrite("Deleting \"erl\" and \"start\" in directory ~ts ...~n",
    │ │ │ │ +              [ErtsBinDir]),
    │ │ │ │ +    file:delete(filename:join([ErtsBinDir, "erl"])),
    │ │ │ │ +    file:delete(filename:join([ErtsBinDir, "start"])),
    │ │ │ │ +
    │ │ │ │ +    io:fwrite("Creating temporary directory ~ts ...~n", [TmpBinDir]),
    │ │ │ │ +    file:make_dir(TmpBinDir),
    │ │ │ │ +
    │ │ │ │ +    io:fwrite("Copying file \"~ts.boot\" to ~ts ...~n",
    │ │ │ │ +              [PlainRelFileName, filename:join([TmpBinDir, "start.boot"])]),
    │ │ │ │ +    copy_file(PlainRelFileName++".boot",filename:join([TmpBinDir, "start.boot"])),
    │ │ │ │  
    │ │ │ │ -    io:fwrite("Copying files \"epmd\", \"run_erl\" and \"to_erl\" from \n"
    │ │ │ │ +    io:fwrite("Copying files \"epmd\", \"run_erl\" and \"to_erl\" from \n"
    │ │ │ │                "~ts to ~ts ...~n",
    │ │ │ │ -              [ErtsBinDir, TmpBinDir]),
    │ │ │ │ -    copy_file(filename:join([ErtsBinDir, "epmd"]),
    │ │ │ │ -              filename:join([TmpBinDir, "epmd"]), [preserve]),
    │ │ │ │ -    copy_file(filename:join([ErtsBinDir, "run_erl"]),
    │ │ │ │ -              filename:join([TmpBinDir, "run_erl"]), [preserve]),
    │ │ │ │ -    copy_file(filename:join([ErtsBinDir, "to_erl"]),
    │ │ │ │ -              filename:join([TmpBinDir, "to_erl"]), [preserve]),
    │ │ │ │ +              [ErtsBinDir, TmpBinDir]),
    │ │ │ │ +    copy_file(filename:join([ErtsBinDir, "epmd"]),
    │ │ │ │ +              filename:join([TmpBinDir, "epmd"]), [preserve]),
    │ │ │ │ +    copy_file(filename:join([ErtsBinDir, "run_erl"]),
    │ │ │ │ +              filename:join([TmpBinDir, "run_erl"]), [preserve]),
    │ │ │ │ +    copy_file(filename:join([ErtsBinDir, "to_erl"]),
    │ │ │ │ +              filename:join([TmpBinDir, "to_erl"]), [preserve]),
    │ │ │ │  
    │ │ │ │      %% This is needed if 'start' script created from 'start.src' shall
    │ │ │ │      %% be used as it points out this directory as log dir for 'run_erl'
    │ │ │ │ -    TmpLogDir = filename:join([TmpDir, "log"]),
    │ │ │ │ -    io:fwrite("Creating temporary directory ~ts ...~n", [TmpLogDir]),
    │ │ │ │ -    ok = file:make_dir(TmpLogDir),
    │ │ │ │ -
    │ │ │ │ -    StartErlDataFile = filename:join([TmpDir, "releases", "start_erl.data"]),
    │ │ │ │ -    io:fwrite("Creating ~ts ...~n", [StartErlDataFile]),
    │ │ │ │ -    StartErlData = io_lib:fwrite("~s ~s~n", [ErtsVsn, RelVsn]),
    │ │ │ │ -    write_file(StartErlDataFile, StartErlData),
    │ │ │ │ -
    │ │ │ │ -    io:fwrite("Recreating tar file ~ts from contents in directory ~ts ...~n",
    │ │ │ │ -	      [TarFileName,TmpDir]),
    │ │ │ │ -    {ok, Tar} = erl_tar:open(TarFileName, [write, compressed]),
    │ │ │ │ +    TmpLogDir = filename:join([TmpDir, "log"]),
    │ │ │ │ +    io:fwrite("Creating temporary directory ~ts ...~n", [TmpLogDir]),
    │ │ │ │ +    ok = file:make_dir(TmpLogDir),
    │ │ │ │ +
    │ │ │ │ +    StartErlDataFile = filename:join([TmpDir, "releases", "start_erl.data"]),
    │ │ │ │ +    io:fwrite("Creating ~ts ...~n", [StartErlDataFile]),
    │ │ │ │ +    StartErlData = io_lib:fwrite("~s ~s~n", [ErtsVsn, RelVsn]),
    │ │ │ │ +    write_file(StartErlDataFile, StartErlData),
    │ │ │ │ +
    │ │ │ │ +    io:fwrite("Recreating tar file ~ts from contents in directory ~ts ...~n",
    │ │ │ │ +	      [TarFileName,TmpDir]),
    │ │ │ │ +    {ok, Tar} = erl_tar:open(TarFileName, [write, compressed]),
    │ │ │ │      %% {ok, Cwd} = file:get_cwd(),
    │ │ │ │      %% file:set_cwd("tmp"),
    │ │ │ │      ErtsDir = "erts-"++ErtsVsn,
    │ │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"bin"), "bin", []),
    │ │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,ErtsDir), ErtsDir, []),
    │ │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"releases"), "releases", []),
    │ │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"lib"), "lib", []),
    │ │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"log"), "log", []),
    │ │ │ │ -    erl_tar:close(Tar),
    │ │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"bin"), "bin", []),
    │ │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,ErtsDir), ErtsDir, []),
    │ │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"releases"), "releases", []),
    │ │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"lib"), "lib", []),
    │ │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"log"), "log", []),
    │ │ │ │ +    erl_tar:close(Tar),
    │ │ │ │      %% file:set_cwd(Cwd),
    │ │ │ │ -    io:fwrite("Removing directory ~ts ...~n",[TmpDir]),
    │ │ │ │ -    remove_dir_tree(TmpDir),
    │ │ │ │ +    io:fwrite("Removing directory ~ts ...~n",[TmpDir]),
    │ │ │ │ +    remove_dir_tree(TmpDir),
    │ │ │ │      ok.
    │ │ │ │  
    │ │ │ │  
    │ │ │ │ -install(RelFileName, RootDir) ->
    │ │ │ │ +install(RelFileName, RootDir) ->
    │ │ │ │      TarFile = RelFileName ++ ".tar.gz",
    │ │ │ │ -    io:fwrite("Extracting ~ts ...~n", [TarFile]),
    │ │ │ │ -    extract_tar(TarFile, RootDir),
    │ │ │ │ -    StartErlDataFile = filename:join([RootDir, "releases", "start_erl.data"]),
    │ │ │ │ -    {ok, StartErlData} = read_txt_file(StartErlDataFile),
    │ │ │ │ -    [ErlVsn, _RelVsn| _] = string:tokens(StartErlData, " \n"),
    │ │ │ │ -    ErtsBinDir = filename:join([RootDir, "erts-" ++ ErlVsn, "bin"]),
    │ │ │ │ -    BinDir = filename:join([RootDir, "bin"]),
    │ │ │ │ -    io:fwrite("Substituting in erl.src, start.src and start_erl.src to "
    │ │ │ │ -              "form erl, start and start_erl ...\n"),
    │ │ │ │ -    subst_src_scripts(["erl", "start", "start_erl"], ErtsBinDir, BinDir,
    │ │ │ │ -                      [{"FINAL_ROOTDIR", RootDir}, {"EMU", "beam"}],
    │ │ │ │ -                      [preserve]),
    │ │ │ │ +    io:fwrite("Extracting ~ts ...~n", [TarFile]),
    │ │ │ │ +    extract_tar(TarFile, RootDir),
    │ │ │ │ +    StartErlDataFile = filename:join([RootDir, "releases", "start_erl.data"]),
    │ │ │ │ +    {ok, StartErlData} = read_txt_file(StartErlDataFile),
    │ │ │ │ +    [ErlVsn, _RelVsn| _] = string:tokens(StartErlData, " \n"),
    │ │ │ │ +    ErtsBinDir = filename:join([RootDir, "erts-" ++ ErlVsn, "bin"]),
    │ │ │ │ +    BinDir = filename:join([RootDir, "bin"]),
    │ │ │ │ +    io:fwrite("Substituting in erl.src, start.src and start_erl.src to "
    │ │ │ │ +              "form erl, start and start_erl ...\n"),
    │ │ │ │ +    subst_src_scripts(["erl", "start", "start_erl"], ErtsBinDir, BinDir,
    │ │ │ │ +                      [{"FINAL_ROOTDIR", RootDir}, {"EMU", "beam"}],
    │ │ │ │ +                      [preserve]),
    │ │ │ │      %%! Workaround for pre OTP 17.0: start.src and start_erl.src did
    │ │ │ │      %%! not have correct permissions, so the above 'preserve' option did not help
    │ │ │ │ -    ok = file:change_mode(filename:join(BinDir,"start"),8#0755),
    │ │ │ │ -    ok = file:change_mode(filename:join(BinDir,"start_erl"),8#0755),
    │ │ │ │ +    ok = file:change_mode(filename:join(BinDir,"start"),8#0755),
    │ │ │ │ +    ok = file:change_mode(filename:join(BinDir,"start_erl"),8#0755),
    │ │ │ │  
    │ │ │ │ -    io:fwrite("Creating the RELEASES file ...\n"),
    │ │ │ │ -    create_RELEASES(RootDir, filename:join([RootDir, "releases",
    │ │ │ │ -					    filename:basename(RelFileName)])).
    │ │ │ │ +    io:fwrite("Creating the RELEASES file ...\n"),
    │ │ │ │ +    create_RELEASES(RootDir, filename:join([RootDir, "releases",
    │ │ │ │ +					    filename:basename(RelFileName)])).
    │ │ │ │  
    │ │ │ │  %% LOCALS
    │ │ │ │  
    │ │ │ │  %% make_script(RelFileName,Opts)
    │ │ │ │  %%
    │ │ │ │ -make_script(RelFileName,Opts) ->
    │ │ │ │ -    systools:make_script(RelFileName, [no_module_tests,
    │ │ │ │ -				       {outdir,filename:dirname(RelFileName)}
    │ │ │ │ -				       |Opts]).
    │ │ │ │ +make_script(RelFileName,Opts) ->
    │ │ │ │ +    systools:make_script(RelFileName, [no_module_tests,
    │ │ │ │ +				       {outdir,filename:dirname(RelFileName)}
    │ │ │ │ +				       |Opts]).
    │ │ │ │  
    │ │ │ │  %% make_tar(RelFileName,Opts)
    │ │ │ │  %%
    │ │ │ │ -make_tar(RelFileName,Opts) ->
    │ │ │ │ -    RootDir = code:root_dir(),
    │ │ │ │ -    systools:make_tar(RelFileName, [{erts, RootDir},
    │ │ │ │ -				    {outdir,filename:dirname(RelFileName)}
    │ │ │ │ -				    |Opts]).
    │ │ │ │ +make_tar(RelFileName,Opts) ->
    │ │ │ │ +    RootDir = code:root_dir(),
    │ │ │ │ +    systools:make_tar(RelFileName, [{erts, RootDir},
    │ │ │ │ +				    {outdir,filename:dirname(RelFileName)}
    │ │ │ │ +				    |Opts]).
    │ │ │ │  
    │ │ │ │  %% extract_tar(TarFile, DestDir)
    │ │ │ │  %%
    │ │ │ │ -extract_tar(TarFile, DestDir) ->
    │ │ │ │ -    erl_tar:extract(TarFile, [{cwd, DestDir}, compressed]).
    │ │ │ │ +extract_tar(TarFile, DestDir) ->
    │ │ │ │ +    erl_tar:extract(TarFile, [{cwd, DestDir}, compressed]).
    │ │ │ │  
    │ │ │ │ -create_RELEASES(DestDir, RelFileName) ->
    │ │ │ │ -    release_handler:create_RELEASES(DestDir, RelFileName ++ ".rel").
    │ │ │ │ +create_RELEASES(DestDir, RelFileName) ->
    │ │ │ │ +    release_handler:create_RELEASES(DestDir, RelFileName ++ ".rel").
    │ │ │ │  
    │ │ │ │ -subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) ->
    │ │ │ │ -    lists:foreach(fun(Script) ->
    │ │ │ │ -                          subst_src_script(Script, SrcDir, DestDir,
    │ │ │ │ -                                           Vars, Opts)
    │ │ │ │ -                  end, Scripts).
    │ │ │ │ -
    │ │ │ │ -subst_src_script(Script, SrcDir, DestDir, Vars, Opts) ->
    │ │ │ │ -    subst_file(filename:join([SrcDir, Script ++ ".src"]),
    │ │ │ │ -               filename:join([DestDir, Script]),
    │ │ │ │ -               Vars, Opts).
    │ │ │ │ -
    │ │ │ │ -subst_file(Src, Dest, Vars, Opts) ->
    │ │ │ │ -    {ok, Conts} = read_txt_file(Src),
    │ │ │ │ -    NConts = subst(Conts, Vars),
    │ │ │ │ -    write_file(Dest, NConts),
    │ │ │ │ -    case lists:member(preserve, Opts) of
    │ │ │ │ +subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) ->
    │ │ │ │ +    lists:foreach(fun(Script) ->
    │ │ │ │ +                          subst_src_script(Script, SrcDir, DestDir,
    │ │ │ │ +                                           Vars, Opts)
    │ │ │ │ +                  end, Scripts).
    │ │ │ │ +
    │ │ │ │ +subst_src_script(Script, SrcDir, DestDir, Vars, Opts) ->
    │ │ │ │ +    subst_file(filename:join([SrcDir, Script ++ ".src"]),
    │ │ │ │ +               filename:join([DestDir, Script]),
    │ │ │ │ +               Vars, Opts).
    │ │ │ │ +
    │ │ │ │ +subst_file(Src, Dest, Vars, Opts) ->
    │ │ │ │ +    {ok, Conts} = read_txt_file(Src),
    │ │ │ │ +    NConts = subst(Conts, Vars),
    │ │ │ │ +    write_file(Dest, NConts),
    │ │ │ │ +    case lists:member(preserve, Opts) of
    │ │ │ │          true ->
    │ │ │ │ -            {ok, FileInfo} = file:read_file_info(Src),
    │ │ │ │ -            file:write_file_info(Dest, FileInfo);
    │ │ │ │ +            {ok, FileInfo} = file:read_file_info(Src),
    │ │ │ │ +            file:write_file_info(Dest, FileInfo);
    │ │ │ │          false ->
    │ │ │ │              ok
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │  %% subst(Str, Vars)
    │ │ │ │  %% Vars = [{Var, Val}]
    │ │ │ │  %% Var = Val = string()
    │ │ │ │  %% Substitute all occurrences of %Var% for Val in Str, using the list
    │ │ │ │  %% of variables in Vars.
    │ │ │ │  %%
    │ │ │ │ -subst(Str, Vars) ->
    │ │ │ │ -    subst(Str, Vars, []).
    │ │ │ │ +subst(Str, Vars) ->
    │ │ │ │ +    subst(Str, Vars, []).
    │ │ │ │  
    │ │ │ │ -subst([$%, C| Rest], Vars, Result) when $A =< C, C =< $Z ->
    │ │ │ │ -    subst_var([C| Rest], Vars, Result, []);
    │ │ │ │ -subst([$%, C| Rest], Vars, Result) when $a =< C, C =< $z ->
    │ │ │ │ -    subst_var([C| Rest], Vars, Result, []);
    │ │ │ │ -subst([$%, C| Rest], Vars, Result) when  C == $_ ->
    │ │ │ │ -    subst_var([C| Rest], Vars, Result, []);
    │ │ │ │ -subst([C| Rest], Vars, Result) ->
    │ │ │ │ -    subst(Rest, Vars, [C| Result]);
    │ │ │ │ -subst([], _Vars, Result) ->
    │ │ │ │ -    lists:reverse(Result).
    │ │ │ │ -
    │ │ │ │ -subst_var([$%| Rest], Vars, Result, VarAcc) ->
    │ │ │ │ -    Key = lists:reverse(VarAcc),
    │ │ │ │ -    case lists:keysearch(Key, 1, Vars) of
    │ │ │ │ -        {value, {Key, Value}} ->
    │ │ │ │ -            subst(Rest, Vars, lists:reverse(Value, Result));
    │ │ │ │ +subst([$%, C| Rest], Vars, Result) when $A =< C, C =< $Z ->
    │ │ │ │ +    subst_var([C| Rest], Vars, Result, []);
    │ │ │ │ +subst([$%, C| Rest], Vars, Result) when $a =< C, C =< $z ->
    │ │ │ │ +    subst_var([C| Rest], Vars, Result, []);
    │ │ │ │ +subst([$%, C| Rest], Vars, Result) when  C == $_ ->
    │ │ │ │ +    subst_var([C| Rest], Vars, Result, []);
    │ │ │ │ +subst([C| Rest], Vars, Result) ->
    │ │ │ │ +    subst(Rest, Vars, [C| Result]);
    │ │ │ │ +subst([], _Vars, Result) ->
    │ │ │ │ +    lists:reverse(Result).
    │ │ │ │ +
    │ │ │ │ +subst_var([$%| Rest], Vars, Result, VarAcc) ->
    │ │ │ │ +    Key = lists:reverse(VarAcc),
    │ │ │ │ +    case lists:keysearch(Key, 1, Vars) of
    │ │ │ │ +        {value, {Key, Value}} ->
    │ │ │ │ +            subst(Rest, Vars, lists:reverse(Value, Result));
    │ │ │ │          false ->
    │ │ │ │ -            subst(Rest, Vars, [$%| VarAcc ++ [$%| Result]])
    │ │ │ │ +            subst(Rest, Vars, [$%| VarAcc ++ [$%| Result]])
    │ │ │ │      end;
    │ │ │ │ -subst_var([C| Rest], Vars, Result, VarAcc) ->
    │ │ │ │ -    subst_var(Rest, Vars, Result, [C| VarAcc]);
    │ │ │ │ -subst_var([], Vars, Result, VarAcc) ->
    │ │ │ │ -    subst([], Vars, [VarAcc ++ [$%| Result]]).
    │ │ │ │ -
    │ │ │ │ -copy_file(Src, Dest) ->
    │ │ │ │ -    copy_file(Src, Dest, []).
    │ │ │ │ -
    │ │ │ │ -copy_file(Src, Dest, Opts) ->
    │ │ │ │ -    {ok,_} = file:copy(Src, Dest),
    │ │ │ │ -    case lists:member(preserve, Opts) of
    │ │ │ │ +subst_var([C| Rest], Vars, Result, VarAcc) ->
    │ │ │ │ +    subst_var(Rest, Vars, Result, [C| VarAcc]);
    │ │ │ │ +subst_var([], Vars, Result, VarAcc) ->
    │ │ │ │ +    subst([], Vars, [VarAcc ++ [$%| Result]]).
    │ │ │ │ +
    │ │ │ │ +copy_file(Src, Dest) ->
    │ │ │ │ +    copy_file(Src, Dest, []).
    │ │ │ │ +
    │ │ │ │ +copy_file(Src, Dest, Opts) ->
    │ │ │ │ +    {ok,_} = file:copy(Src, Dest),
    │ │ │ │ +    case lists:member(preserve, Opts) of
    │ │ │ │          true ->
    │ │ │ │ -            {ok, FileInfo} = file:read_file_info(Src),
    │ │ │ │ -            file:write_file_info(Dest, FileInfo);
    │ │ │ │ +            {ok, FileInfo} = file:read_file_info(Src),
    │ │ │ │ +            file:write_file_info(Dest, FileInfo);
    │ │ │ │          false ->
    │ │ │ │              ok
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -write_file(FName, Conts) ->
    │ │ │ │ -    Enc = file:native_name_encoding(),
    │ │ │ │ -    {ok, Fd} = file:open(FName, [write]),
    │ │ │ │ -    file:write(Fd, unicode:characters_to_binary(Conts,Enc,Enc)),
    │ │ │ │ -    file:close(Fd).
    │ │ │ │ -
    │ │ │ │ -read_txt_file(File) ->
    │ │ │ │ -    {ok, Bin} = file:read_file(File),
    │ │ │ │ -    {ok, binary_to_list(Bin)}.
    │ │ │ │ -
    │ │ │ │ -remove_dir_tree(Dir) ->
    │ │ │ │ -    remove_all_files(".", [Dir]).
    │ │ │ │ -
    │ │ │ │ -remove_all_files(Dir, Files) ->
    │ │ │ │ -    lists:foreach(fun(File) ->
    │ │ │ │ -                          FilePath = filename:join([Dir, File]),
    │ │ │ │ -                          case filelib:is_dir(FilePath) of
    │ │ │ │ +write_file(FName, Conts) ->
    │ │ │ │ +    Enc = file:native_name_encoding(),
    │ │ │ │ +    {ok, Fd} = file:open(FName, [write]),
    │ │ │ │ +    file:write(Fd, unicode:characters_to_binary(Conts,Enc,Enc)),
    │ │ │ │ +    file:close(Fd).
    │ │ │ │ +
    │ │ │ │ +read_txt_file(File) ->
    │ │ │ │ +    {ok, Bin} = file:read_file(File),
    │ │ │ │ +    {ok, binary_to_list(Bin)}.
    │ │ │ │ +
    │ │ │ │ +remove_dir_tree(Dir) ->
    │ │ │ │ +    remove_all_files(".", [Dir]).
    │ │ │ │ +
    │ │ │ │ +remove_all_files(Dir, Files) ->
    │ │ │ │ +    lists:foreach(fun(File) ->
    │ │ │ │ +                          FilePath = filename:join([Dir, File]),
    │ │ │ │ +                          case filelib:is_dir(FilePath) of
    │ │ │ │                                true ->
    │ │ │ │ -                                  {ok, DirFiles} = file:list_dir(FilePath),
    │ │ │ │ -                                  remove_all_files(FilePath, DirFiles),
    │ │ │ │ -                                  file:del_dir(FilePath);
    │ │ │ │ +                                  {ok, DirFiles} = file:list_dir(FilePath),
    │ │ │ │ +                                  remove_all_files(FilePath, DirFiles),
    │ │ │ │ +                                  file:del_dir(FilePath);
    │ │ │ │                                _ ->
    │ │ │ │ -                                  file:delete(FilePath)
    │ │ │ │ +                                  file:delete(FilePath)
    │ │ │ │                            end
    │ │ │ │ -                  end, Files).
    │ │ │ │ + end, Files).
    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/content.opf │ │ │ │ ├── OEBPS/content.opf │ │ │ │ │ @@ -1,14 +1,14 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Erlang System Documentation - 29.0-rc2 │ │ │ │ │ - urn:uuid:cb76a0c4-08e2-4f01-1d91-083e6d278126 │ │ │ │ │ + urn:uuid:8946b31e-254f-8098-b0c4-8d0cca7b4231 │ │ │ │ │ en │ │ │ │ │ - 2026-03-19T09:42:29Z │ │ │ │ │ + 2026-03-19T15:36:59Z │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -86,22 +86,22 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ - │ │ │ │ │ + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ - │ │ │ │ │ │ │ │ │ │ - │ │ │ │ │ + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/conc_prog.xhtml │ │ │ │ @@ -37,107 +37,107 @@ │ │ │ │ threads of execution in an Erlang program and to allow these threads to │ │ │ │ communicate with each other. In Erlang, each thread of execution is called a │ │ │ │ process.

    (Aside: the term "process" is usually used when the threads of execution share │ │ │ │ no data with each other and the term "thread" when they share data in some way. │ │ │ │ Threads of execution in Erlang share no data, that is why they are called │ │ │ │ processes).

    The Erlang BIF spawn is used to create a new process: │ │ │ │ spawn(Module, Exported_Function, List of Arguments). Consider the following │ │ │ │ -module:

    -module(tut14).
    │ │ │ │ +module:

    -module(tut14).
    │ │ │ │  
    │ │ │ │ --export([start/0, say_something/2]).
    │ │ │ │ +-export([start/0, say_something/2]).
    │ │ │ │  
    │ │ │ │ -say_something(_What, 0) ->
    │ │ │ │ +say_something(_What, 0) ->
    │ │ │ │      done;
    │ │ │ │ -say_something(What, Times) ->
    │ │ │ │ -    io:format("~p~n", [What]),
    │ │ │ │ -    say_something(What, Times - 1).
    │ │ │ │ -
    │ │ │ │ -start() ->
    │ │ │ │ -    spawn(tut14, say_something, [hello, 3]),
    │ │ │ │ -    spawn(tut14, say_something, [goodbye, 3]).
    5> c(tut14).
    │ │ │ │ -{ok,tut14}
    │ │ │ │ -6> tut14:say_something(hello, 3).
    │ │ │ │ +say_something(What, Times) ->
    │ │ │ │ +    io:format("~p~n", [What]),
    │ │ │ │ +    say_something(What, Times - 1).
    │ │ │ │ +
    │ │ │ │ +start() ->
    │ │ │ │ +    spawn(tut14, say_something, [hello, 3]),
    │ │ │ │ +    spawn(tut14, say_something, [goodbye, 3]).
    5> c(tut14).
    │ │ │ │ +{ok,tut14}
    │ │ │ │ +6> tut14:say_something(hello, 3).
    │ │ │ │  hello
    │ │ │ │  hello
    │ │ │ │  hello
    │ │ │ │  done

    As shown, the function say_something writes its first argument the number of │ │ │ │ times specified by second argument. The function start starts two Erlang │ │ │ │ processes, one that writes "hello" three times and one that writes "goodbye" │ │ │ │ three times. Both processes use the function say_something. Notice that a │ │ │ │ function used in this way by spawn, to start a process, must be exported from │ │ │ │ -the module (that is, in the -export at the start of the module).

    9> tut14:start().
    │ │ │ │ +the module (that is, in the -export at the start of the module).

    9> tut14:start().
    │ │ │ │  hello
    │ │ │ │  goodbye
    │ │ │ │  <0.63.0>
    │ │ │ │  hello
    │ │ │ │  goodbye
    │ │ │ │  hello
    │ │ │ │  goodbye

    Notice that it did not write "hello" three times and then "goodbye" three times. │ │ │ │ Instead, the first process wrote a "hello", the second a "goodbye", the first │ │ │ │ another "hello" and so forth. But where did the <0.63.0> come from? The return │ │ │ │ value of a function is the return value of the last "thing" in the function. The │ │ │ │ -last thing in the function start is

    spawn(tut14, say_something, [goodbye, 3]).

    spawn returns a process identifier, or pid, which uniquely identifies the │ │ │ │ +last thing in the function start is

    spawn(tut14, say_something, [goodbye, 3]).

    spawn returns a process identifier, or pid, which uniquely identifies the │ │ │ │ process. So <0.63.0> is the pid of the spawn function call above. The next │ │ │ │ example shows how to use pids.

    Notice also that ~p is used instead of ~w in io:format/2. To quote the manual:

    ~p Writes the data with standard syntax in the same way as ~w, but breaks terms │ │ │ │ whose printed representation is longer than one line into many lines and indents │ │ │ │ each line sensibly. It also tries to detect flat lists of printable characters and │ │ │ │ to output these as strings

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Message Passing │ │ │ │

    │ │ │ │

    In the following example two processes are created and they send messages to │ │ │ │ -each other a number of times.

    -module(tut15).
    │ │ │ │ +each other a number of times.

    -module(tut15).
    │ │ │ │  
    │ │ │ │ --export([start/0, ping/2, pong/0]).
    │ │ │ │ +-export([start/0, ping/2, pong/0]).
    │ │ │ │  
    │ │ │ │ -ping(0, Pong_PID) ->
    │ │ │ │ +ping(0, Pong_PID) ->
    │ │ │ │      Pong_PID ! finished,
    │ │ │ │ -    io:format("ping finished~n", []);
    │ │ │ │ +    io:format("ping finished~n", []);
    │ │ │ │  
    │ │ │ │ -ping(N, Pong_PID) ->
    │ │ │ │ -    Pong_PID ! {ping, self()},
    │ │ │ │ +ping(N, Pong_PID) ->
    │ │ │ │ +    Pong_PID ! {ping, self()},
    │ │ │ │      receive
    │ │ │ │          pong ->
    │ │ │ │ -            io:format("Ping received pong~n", [])
    │ │ │ │ +            io:format("Ping received pong~n", [])
    │ │ │ │      end,
    │ │ │ │ -    ping(N - 1, Pong_PID).
    │ │ │ │ +    ping(N - 1, Pong_PID).
    │ │ │ │  
    │ │ │ │ -pong() ->
    │ │ │ │ +pong() ->
    │ │ │ │      receive
    │ │ │ │          finished ->
    │ │ │ │ -            io:format("Pong finished~n", []);
    │ │ │ │ -        {ping, Ping_PID} ->
    │ │ │ │ -            io:format("Pong received ping~n", []),
    │ │ │ │ +            io:format("Pong finished~n", []);
    │ │ │ │ +        {ping, Ping_PID} ->
    │ │ │ │ +            io:format("Pong received ping~n", []),
    │ │ │ │              Ping_PID ! pong,
    │ │ │ │ -            pong()
    │ │ │ │ +            pong()
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -start() ->
    │ │ │ │ -    Pong_PID = spawn(tut15, pong, []),
    │ │ │ │ -    spawn(tut15, ping, [3, Pong_PID]).
    1> c(tut15).
    │ │ │ │ -{ok,tut15}
    │ │ │ │ -2> tut15: start().
    │ │ │ │ +start() ->
    │ │ │ │ +    Pong_PID = spawn(tut15, pong, []),
    │ │ │ │ +    spawn(tut15, ping, [3, Pong_PID]).
    1> c(tut15).
    │ │ │ │ +{ok,tut15}
    │ │ │ │ +2> tut15: start().
    │ │ │ │  <0.36.0>
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  ping finished
    │ │ │ │ -Pong finished

    The function start first creates a process, let us call it "pong":

    Pong_PID = spawn(tut15, pong, [])

    This process executes tut15:pong(). Pong_PID is the process identity of the │ │ │ │ -"pong" process. The function start now creates another process "ping":

    spawn(tut15, ping, [3, Pong_PID]),

    This process executes:

    tut15:ping(3, Pong_PID)

    <0.36.0> is the return value from the start function.

    The process "pong" now does:

    receive
    │ │ │ │ +Pong finished

    The function start first creates a process, let us call it "pong":

    Pong_PID = spawn(tut15, pong, [])

    This process executes tut15:pong(). Pong_PID is the process identity of the │ │ │ │ +"pong" process. The function start now creates another process "ping":

    spawn(tut15, ping, [3, Pong_PID]),

    This process executes:

    tut15:ping(3, Pong_PID)

    <0.36.0> is the return value from the start function.

    The process "pong" now does:

    receive
    │ │ │ │      finished ->
    │ │ │ │ -        io:format("Pong finished~n", []);
    │ │ │ │ -    {ping, Ping_PID} ->
    │ │ │ │ -        io:format("Pong received ping~n", []),
    │ │ │ │ +        io:format("Pong finished~n", []);
    │ │ │ │ +    {ping, Ping_PID} ->
    │ │ │ │ +        io:format("Pong received ping~n", []),
    │ │ │ │          Ping_PID ! pong,
    │ │ │ │ -        pong()
    │ │ │ │ +        pong()
    │ │ │ │  end.

    The receive construct is used to allow processes to wait for messages from │ │ │ │ other processes. It has the following format:

    receive
    │ │ │ │     pattern1 ->
    │ │ │ │         actions1;
    │ │ │ │     pattern2 ->
    │ │ │ │         actions2;
    │ │ │ │     ....
    │ │ │ │ @@ -158,84 +158,84 @@
    │ │ │ │  queue (keeping the first message and any other messages in the queue). If the
    │ │ │ │  second message does not match, the third message is tried, and so on, until the
    │ │ │ │  end of the queue is reached. If the end of the queue is reached, the process
    │ │ │ │  blocks (stops execution) and waits until a new message is received and this
    │ │ │ │  procedure is repeated.

    The Erlang implementation is "clever" and minimizes the number of times each │ │ │ │ message is tested against the patterns in each receive.

    Now back to the ping pong example.

    "Pong" is waiting for messages. If the atom finished is received, "pong" │ │ │ │ writes "Pong finished" to the output and, as it has nothing more to do, │ │ │ │ -terminates. If it receives a message with the format:

    {ping, Ping_PID}

    it writes "Pong received ping" to the output and sends the atom pong to the │ │ │ │ +terminates. If it receives a message with the format:

    {ping, Ping_PID}

    it writes "Pong received ping" to the output and sends the atom pong to the │ │ │ │ process "ping":

    Ping_PID ! pong

    Notice how the operator "!" is used to send messages. The syntax of "!" is:

    Pid ! Message

    That is, Message (any Erlang term) is sent to the process with identity Pid.

    After sending the message pong to the process "ping", "pong" calls the pong │ │ │ │ function again, which causes it to get back to the receive again and wait for │ │ │ │ -another message.

    Now let us look at the process "ping". Recall that it was started by executing:

    tut15:ping(3, Pong_PID)

    Looking at the function ping/2, the second clause of ping/2 is executed │ │ │ │ +another message.

    Now let us look at the process "ping". Recall that it was started by executing:

    tut15:ping(3, Pong_PID)

    Looking at the function ping/2, the second clause of ping/2 is executed │ │ │ │ since the value of the first argument is 3 (not 0) (first clause head is │ │ │ │ -ping(0,Pong_PID), second clause head is ping(N,Pong_PID), so N becomes 3).

    The second clause sends a message to "pong":

    Pong_PID ! {ping, self()},

    self/0 returns the pid of the process that executes self/0, in this case the │ │ │ │ +ping(0,Pong_PID), second clause head is ping(N,Pong_PID), so N becomes 3).

    The second clause sends a message to "pong":

    Pong_PID ! {ping, self()},

    self/0 returns the pid of the process that executes self/0, in this case the │ │ │ │ pid of "ping". (Recall the code for "pong", this lands up in the variable │ │ │ │ Ping_PID in the receive previously explained.)

    "Ping" now waits for a reply from "pong":

    receive
    │ │ │ │      pong ->
    │ │ │ │ -        io:format("Ping received pong~n", [])
    │ │ │ │ +        io:format("Ping received pong~n", [])
    │ │ │ │  end,

    It writes "Ping received pong" when this reply arrives, after which "ping" calls │ │ │ │ -the ping function again.

    ping(N - 1, Pong_PID)

    N-1 causes the first argument to be decremented until it becomes 0. When this │ │ │ │ -occurs, the first clause of ping/2 is executed:

    ping(0, Pong_PID) ->
    │ │ │ │ +the ping function again.

    ping(N - 1, Pong_PID)

    N-1 causes the first argument to be decremented until it becomes 0. When this │ │ │ │ +occurs, the first clause of ping/2 is executed:

    ping(0, Pong_PID) ->
    │ │ │ │      Pong_PID !  finished,
    │ │ │ │ -    io:format("ping finished~n", []);

    The atom finished is sent to "pong" (causing it to terminate as described │ │ │ │ + io:format("ping finished~n", []);

    The atom finished is sent to "pong" (causing it to terminate as described │ │ │ │ above) and "ping finished" is written to the output. "Ping" then terminates as │ │ │ │ it has nothing left to do.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Registered Process Names │ │ │ │

    │ │ │ │

    In the above example, "pong" was first created to be able to give the identity │ │ │ │ of "pong" when "ping" was started. That is, in some way "ping" must be able to │ │ │ │ know the identity of "pong" to be able to send a message to it. Sometimes │ │ │ │ processes which need to know each other's identities are started independently │ │ │ │ of each other. Erlang thus provides a mechanism for processes to be given names │ │ │ │ so that these names can be used as identities instead of pids. This is done by │ │ │ │ -using the register BIF:

    register(some_atom, Pid)

    Let us now rewrite the ping pong example using this and give the name pong to │ │ │ │ -the "pong" process:

    -module(tut16).
    │ │ │ │ +using the register BIF:

    register(some_atom, Pid)

    Let us now rewrite the ping pong example using this and give the name pong to │ │ │ │ +the "pong" process:

    -module(tut16).
    │ │ │ │  
    │ │ │ │ --export([start/0, ping/1, pong/0]).
    │ │ │ │ +-export([start/0, ping/1, pong/0]).
    │ │ │ │  
    │ │ │ │ -ping(0) ->
    │ │ │ │ +ping(0) ->
    │ │ │ │      pong ! finished,
    │ │ │ │ -    io:format("ping finished~n", []);
    │ │ │ │ +    io:format("ping finished~n", []);
    │ │ │ │  
    │ │ │ │ -ping(N) ->
    │ │ │ │ -    pong ! {ping, self()},
    │ │ │ │ +ping(N) ->
    │ │ │ │ +    pong ! {ping, self()},
    │ │ │ │      receive
    │ │ │ │          pong ->
    │ │ │ │ -            io:format("Ping received pong~n", [])
    │ │ │ │ +            io:format("Ping received pong~n", [])
    │ │ │ │      end,
    │ │ │ │ -    ping(N - 1).
    │ │ │ │ +    ping(N - 1).
    │ │ │ │  
    │ │ │ │ -pong() ->
    │ │ │ │ +pong() ->
    │ │ │ │      receive
    │ │ │ │          finished ->
    │ │ │ │ -            io:format("Pong finished~n", []);
    │ │ │ │ -        {ping, Ping_PID} ->
    │ │ │ │ -            io:format("Pong received ping~n", []),
    │ │ │ │ +            io:format("Pong finished~n", []);
    │ │ │ │ +        {ping, Ping_PID} ->
    │ │ │ │ +            io:format("Pong received ping~n", []),
    │ │ │ │              Ping_PID ! pong,
    │ │ │ │ -            pong()
    │ │ │ │ +            pong()
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -start() ->
    │ │ │ │ -    register(pong, spawn(tut16, pong, [])),
    │ │ │ │ -    spawn(tut16, ping, [3]).
    2> c(tut16).
    │ │ │ │ -{ok, tut16}
    │ │ │ │ -3> tut16:start().
    │ │ │ │ +start() ->
    │ │ │ │ +    register(pong, spawn(tut16, pong, [])),
    │ │ │ │ +    spawn(tut16, ping, [3]).
    2> c(tut16).
    │ │ │ │ +{ok, tut16}
    │ │ │ │ +3> tut16:start().
    │ │ │ │  <0.38.0>
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  ping finished
    │ │ │ │ -Pong finished

    Here the start/0 function,

    register(pong, spawn(tut16, pong, [])),

    both spawns the "pong" process and gives it the name pong. In the "ping" │ │ │ │ -process, messages can be sent to pong by:

    pong ! {ping, self()},

    ping/2 now becomes ping/1 as the argument Pong_PID is not needed.

    │ │ │ │ +Pong finished

    Here the start/0 function,

    register(pong, spawn(tut16, pong, [])),

    both spawns the "pong" process and gives it the name pong. In the "ping" │ │ │ │ +process, messages can be sent to pong by:

    pong ! {ping, self()},

    ping/2 now becomes ping/1 as the argument Pong_PID is not needed.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Distributed Programming │ │ │ │

    │ │ │ │

    Let us rewrite the ping pong program with "ping" and "pong" on different │ │ │ │ computers. First a few things are needed to set up to get this to work. The │ │ │ │ @@ -255,106 +255,106 @@ │ │ │ │ of the file. This is a requirement.

    When you start an Erlang system that is going to talk to other Erlang systems, │ │ │ │ you must give it a name, for example:

    $ erl -sname my_name

    We will see more details of this later. If you want to experiment with │ │ │ │ distributed Erlang, but you only have one computer to work on, you can start two │ │ │ │ separate Erlang systems on the same computer but give them different names. Each │ │ │ │ Erlang system running on a computer is called an Erlang node.

    (Note: erl -sname assumes that all nodes are in the same IP domain and we can │ │ │ │ use only the first component of the IP address, if we want to use nodes in │ │ │ │ different domains we use -name instead, but then all IP address must be given │ │ │ │ -in full.)

    Here is the ping pong example modified to run on two separate nodes:

    -module(tut17).
    │ │ │ │ +in full.)

    Here is the ping pong example modified to run on two separate nodes:

    -module(tut17).
    │ │ │ │  
    │ │ │ │ --export([start_ping/1, start_pong/0,  ping/2, pong/0]).
    │ │ │ │ +-export([start_ping/1, start_pong/0,  ping/2, pong/0]).
    │ │ │ │  
    │ │ │ │ -ping(0, Pong_Node) ->
    │ │ │ │ -    {pong, Pong_Node} ! finished,
    │ │ │ │ -    io:format("ping finished~n", []);
    │ │ │ │ +ping(0, Pong_Node) ->
    │ │ │ │ +    {pong, Pong_Node} ! finished,
    │ │ │ │ +    io:format("ping finished~n", []);
    │ │ │ │  
    │ │ │ │ -ping(N, Pong_Node) ->
    │ │ │ │ -    {pong, Pong_Node} ! {ping, self()},
    │ │ │ │ +ping(N, Pong_Node) ->
    │ │ │ │ +    {pong, Pong_Node} ! {ping, self()},
    │ │ │ │      receive
    │ │ │ │          pong ->
    │ │ │ │ -            io:format("Ping received pong~n", [])
    │ │ │ │ +            io:format("Ping received pong~n", [])
    │ │ │ │      end,
    │ │ │ │ -    ping(N - 1, Pong_Node).
    │ │ │ │ +    ping(N - 1, Pong_Node).
    │ │ │ │  
    │ │ │ │ -pong() ->
    │ │ │ │ +pong() ->
    │ │ │ │      receive
    │ │ │ │          finished ->
    │ │ │ │ -            io:format("Pong finished~n", []);
    │ │ │ │ -        {ping, Ping_PID} ->
    │ │ │ │ -            io:format("Pong received ping~n", []),
    │ │ │ │ +            io:format("Pong finished~n", []);
    │ │ │ │ +        {ping, Ping_PID} ->
    │ │ │ │ +            io:format("Pong received ping~n", []),
    │ │ │ │              Ping_PID ! pong,
    │ │ │ │ -            pong()
    │ │ │ │ +            pong()
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -start_pong() ->
    │ │ │ │ -    register(pong, spawn(tut17, pong, [])).
    │ │ │ │ +start_pong() ->
    │ │ │ │ +    register(pong, spawn(tut17, pong, [])).
    │ │ │ │  
    │ │ │ │ -start_ping(Pong_Node) ->
    │ │ │ │ -    spawn(tut17, ping, [3, Pong_Node]).

    Let us assume there are two computers called gollum and kosken. First a node is │ │ │ │ +start_ping(Pong_Node) -> │ │ │ │ + spawn(tut17, ping, [3, Pong_Node]).

    Let us assume there are two computers called gollum and kosken. First a node is │ │ │ │ started on kosken, called ping, and then a node on gollum, called pong.

    On kosken (on a Linux/UNIX system):

    kosken> erl -sname ping
    │ │ │ │  Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0]
    │ │ │ │  
    │ │ │ │  Eshell V5.2.3.7  (abort with ^G)
    │ │ │ │  (ping@kosken)1>

    On gollum:

    gollum> erl -sname pong
    │ │ │ │  Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0]
    │ │ │ │  
    │ │ │ │  Eshell V5.2.3.7  (abort with ^G)
    │ │ │ │ -(pong@gollum)1>

    Now the "pong" process on gollum is started:

    (pong@gollum)1> tut17:start_pong().
    │ │ │ │ +(pong@gollum)1>

    Now the "pong" process on gollum is started:

    (pong@gollum)1> tut17:start_pong().
    │ │ │ │  true

    And the "ping" process on kosken is started (from the code above you can see │ │ │ │ that a parameter of the start_ping function is the node name of the Erlang │ │ │ │ -system where "pong" is running):

    (ping@kosken)1> tut17:start_ping(pong@gollum).
    │ │ │ │ +system where "pong" is running):

    (ping@kosken)1> tut17:start_ping(pong@gollum).
    │ │ │ │  <0.37.0>
    │ │ │ │  Ping received pong
    │ │ │ │  Ping received pong
    │ │ │ │  Ping received pong
    │ │ │ │  ping finished

    As shown, the ping pong program has run. On the "pong" side:

    (pong@gollum)2> 
    │ │ │ │  Pong received ping
    │ │ │ │  Pong received ping
    │ │ │ │  Pong received ping
    │ │ │ │  Pong finished
    │ │ │ │ -(pong@gollum)2> 

    Looking at the tut17 code, you see that the pong function itself is │ │ │ │ +(pong@gollum)2>

    Looking at the tut17 code, you see that the pong function itself is │ │ │ │ unchanged, the following lines work in the same way irrespective of on which │ │ │ │ -node the "ping" process is executes:

    {ping, Ping_PID} ->
    │ │ │ │ -    io:format("Pong received ping~n", []),
    │ │ │ │ +node the "ping" process is executes:

    {ping, Ping_PID} ->
    │ │ │ │ +    io:format("Pong received ping~n", []),
    │ │ │ │      Ping_PID ! pong,

    Thus, Erlang pids contain information about where the process executes. So if │ │ │ │ you know the pid of a process, the ! operator can be used to send it a │ │ │ │ -message disregarding if the process is on the same node or on a different node.

    A difference is how messages are sent to a registered process on another node:

    {pong, Pong_Node} ! {ping, self()},

    A tuple {registered_name,node_name} is used instead of just the │ │ │ │ +message disregarding if the process is on the same node or on a different node.

    A difference is how messages are sent to a registered process on another node:

    {pong, Pong_Node} ! {ping, self()},

    A tuple {registered_name,node_name} is used instead of just the │ │ │ │ registered_name.

    In the previous example, "ping" and "pong" were started from the shells of two │ │ │ │ separate Erlang nodes. spawn can also be used to start processes in other │ │ │ │ nodes.

    The next example is the ping pong program, yet again, but this time "ping" is │ │ │ │ -started in another node:

    -module(tut18).
    │ │ │ │ +started in another node:

    -module(tut18).
    │ │ │ │  
    │ │ │ │ --export([start/1,  ping/2, pong/0]).
    │ │ │ │ +-export([start/1,  ping/2, pong/0]).
    │ │ │ │  
    │ │ │ │ -ping(0, Pong_Node) ->
    │ │ │ │ -    {pong, Pong_Node} ! finished,
    │ │ │ │ -    io:format("ping finished~n", []);
    │ │ │ │ +ping(0, Pong_Node) ->
    │ │ │ │ +    {pong, Pong_Node} ! finished,
    │ │ │ │ +    io:format("ping finished~n", []);
    │ │ │ │  
    │ │ │ │ -ping(N, Pong_Node) ->
    │ │ │ │ -    {pong, Pong_Node} ! {ping, self()},
    │ │ │ │ +ping(N, Pong_Node) ->
    │ │ │ │ +    {pong, Pong_Node} ! {ping, self()},
    │ │ │ │      receive
    │ │ │ │          pong ->
    │ │ │ │ -            io:format("Ping received pong~n", [])
    │ │ │ │ +            io:format("Ping received pong~n", [])
    │ │ │ │      end,
    │ │ │ │ -    ping(N - 1, Pong_Node).
    │ │ │ │ +    ping(N - 1, Pong_Node).
    │ │ │ │  
    │ │ │ │ -pong() ->
    │ │ │ │ +pong() ->
    │ │ │ │      receive
    │ │ │ │          finished ->
    │ │ │ │ -            io:format("Pong finished~n", []);
    │ │ │ │ -        {ping, Ping_PID} ->
    │ │ │ │ -            io:format("Pong received ping~n", []),
    │ │ │ │ +            io:format("Pong finished~n", []);
    │ │ │ │ +        {ping, Ping_PID} ->
    │ │ │ │ +            io:format("Pong received ping~n", []),
    │ │ │ │              Ping_PID ! pong,
    │ │ │ │ -            pong()
    │ │ │ │ +            pong()
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -start(Ping_Node) ->
    │ │ │ │ -    register(pong, spawn(tut18, pong, [])),
    │ │ │ │ -    spawn(Ping_Node, tut18, ping, [3, node()]).

    Assuming an Erlang system called ping (but not the "ping" process) has already │ │ │ │ -been started on kosken, then on gollum this is done:

    (pong@gollum)1> tut18:start(ping@kosken).
    │ │ │ │ +start(Ping_Node) ->
    │ │ │ │ +    register(pong, spawn(tut18, pong, [])),
    │ │ │ │ +    spawn(Ping_Node, tut18, ping, [3, node()]).

    Assuming an Erlang system called ping (but not the "ping" process) has already │ │ │ │ +been started on kosken, then on gollum this is done:

    (pong@gollum)1> tut18:start(ping@kosken).
    │ │ │ │  <3934.39.0>
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │ @@ -421,184 +421,184 @@
    │ │ │ │  %%% Started: messenger:client(Server_Node, Name)
    │ │ │ │  %%% To client: logoff
    │ │ │ │  %%% To client: {message_to, ToName, Message}
    │ │ │ │  %%%
    │ │ │ │  %%% Configuration: change the server_node() function to return the
    │ │ │ │  %%% name of the node where the messenger server runs
    │ │ │ │  
    │ │ │ │ --module(messenger).
    │ │ │ │ --export([start_server/0, server/1, logon/1, logoff/0, message/2, client/2]).
    │ │ │ │ +-module(messenger).
    │ │ │ │ +-export([start_server/0, server/1, logon/1, logoff/0, message/2, client/2]).
    │ │ │ │  
    │ │ │ │  %%% Change the function below to return the name of the node where the
    │ │ │ │  %%% messenger server runs
    │ │ │ │ -server_node() ->
    │ │ │ │ +server_node() ->
    │ │ │ │      messenger@super.
    │ │ │ │  
    │ │ │ │  %%% This is the server process for the "messenger"
    │ │ │ │  %%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
    │ │ │ │ -server(User_List) ->
    │ │ │ │ +server(User_List) ->
    │ │ │ │      receive
    │ │ │ │ -        {From, logon, Name} ->
    │ │ │ │ -            New_User_List = server_logon(From, Name, User_List),
    │ │ │ │ -            server(New_User_List);
    │ │ │ │ -        {From, logoff} ->
    │ │ │ │ -            New_User_List = server_logoff(From, User_List),
    │ │ │ │ -            server(New_User_List);
    │ │ │ │ -        {From, message_to, To, Message} ->
    │ │ │ │ -            server_transfer(From, To, Message, User_List),
    │ │ │ │ -            io:format("list is now: ~p~n", [User_List]),
    │ │ │ │ -            server(User_List)
    │ │ │ │ +        {From, logon, Name} ->
    │ │ │ │ +            New_User_List = server_logon(From, Name, User_List),
    │ │ │ │ +            server(New_User_List);
    │ │ │ │ +        {From, logoff} ->
    │ │ │ │ +            New_User_List = server_logoff(From, User_List),
    │ │ │ │ +            server(New_User_List);
    │ │ │ │ +        {From, message_to, To, Message} ->
    │ │ │ │ +            server_transfer(From, To, Message, User_List),
    │ │ │ │ +            io:format("list is now: ~p~n", [User_List]),
    │ │ │ │ +            server(User_List)
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │  %%% Start the server
    │ │ │ │ -start_server() ->
    │ │ │ │ -    register(messenger, spawn(messenger, server, [[]])).
    │ │ │ │ +start_server() ->
    │ │ │ │ +    register(messenger, spawn(messenger, server, [[]])).
    │ │ │ │  
    │ │ │ │  
    │ │ │ │  %%% Server adds a new user to the user list
    │ │ │ │ -server_logon(From, Name, User_List) ->
    │ │ │ │ +server_logon(From, Name, User_List) ->
    │ │ │ │      %% check if logged on anywhere else
    │ │ │ │ -    case lists:keymember(Name, 2, User_List) of
    │ │ │ │ +    case lists:keymember(Name, 2, User_List) of
    │ │ │ │          true ->
    │ │ │ │ -            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
    │ │ │ │ +            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
    │ │ │ │              User_List;
    │ │ │ │          false ->
    │ │ │ │ -            From ! {messenger, logged_on},
    │ │ │ │ -            [{From, Name} | User_List]        %add user to the list
    │ │ │ │ +            From ! {messenger, logged_on},
    │ │ │ │ +            [{From, Name} | User_List]        %add user to the list
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │  %%% Server deletes a user from the user list
    │ │ │ │ -server_logoff(From, User_List) ->
    │ │ │ │ -    lists:keydelete(From, 1, User_List).
    │ │ │ │ +server_logoff(From, User_List) ->
    │ │ │ │ +    lists:keydelete(From, 1, User_List).
    │ │ │ │  
    │ │ │ │  
    │ │ │ │  %%% Server transfers a message between user
    │ │ │ │ -server_transfer(From, To, Message, User_List) ->
    │ │ │ │ +server_transfer(From, To, Message, User_List) ->
    │ │ │ │      %% check that the user is logged on and who he is
    │ │ │ │ -    case lists:keysearch(From, 1, User_List) of
    │ │ │ │ +    case lists:keysearch(From, 1, User_List) of
    │ │ │ │          false ->
    │ │ │ │ -            From ! {messenger, stop, you_are_not_logged_on};
    │ │ │ │ -        {value, {From, Name}} ->
    │ │ │ │ -            server_transfer(From, Name, To, Message, User_List)
    │ │ │ │ +            From ! {messenger, stop, you_are_not_logged_on};
    │ │ │ │ +        {value, {From, Name}} ->
    │ │ │ │ +            server_transfer(From, Name, To, Message, User_List)
    │ │ │ │      end.
    │ │ │ │  %%% If the user exists, send the message
    │ │ │ │ -server_transfer(From, Name, To, Message, User_List) ->
    │ │ │ │ +server_transfer(From, Name, To, Message, User_List) ->
    │ │ │ │      %% Find the receiver and send the message
    │ │ │ │ -    case lists:keysearch(To, 2, User_List) of
    │ │ │ │ +    case lists:keysearch(To, 2, User_List) of
    │ │ │ │          false ->
    │ │ │ │ -            From ! {messenger, receiver_not_found};
    │ │ │ │ -        {value, {ToPid, To}} ->
    │ │ │ │ -            ToPid ! {message_from, Name, Message},
    │ │ │ │ -            From ! {messenger, sent}
    │ │ │ │ +            From ! {messenger, receiver_not_found};
    │ │ │ │ +        {value, {ToPid, To}} ->
    │ │ │ │ +            ToPid ! {message_from, Name, Message},
    │ │ │ │ +            From ! {messenger, sent}
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │  
    │ │ │ │  %%% User Commands
    │ │ │ │ -logon(Name) ->
    │ │ │ │ -    case whereis(mess_client) of
    │ │ │ │ +logon(Name) ->
    │ │ │ │ +    case whereis(mess_client) of
    │ │ │ │          undefined ->
    │ │ │ │ -            register(mess_client,
    │ │ │ │ -                     spawn(messenger, client, [server_node(), Name]));
    │ │ │ │ +            register(mess_client,
    │ │ │ │ +                     spawn(messenger, client, [server_node(), Name]));
    │ │ │ │          _ -> already_logged_on
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -logoff() ->
    │ │ │ │ +logoff() ->
    │ │ │ │      mess_client ! logoff.
    │ │ │ │  
    │ │ │ │ -message(ToName, Message) ->
    │ │ │ │ -    case whereis(mess_client) of % Test if the client is running
    │ │ │ │ +message(ToName, Message) ->
    │ │ │ │ +    case whereis(mess_client) of % Test if the client is running
    │ │ │ │          undefined ->
    │ │ │ │              not_logged_on;
    │ │ │ │ -        _ -> mess_client ! {message_to, ToName, Message},
    │ │ │ │ +        _ -> mess_client ! {message_to, ToName, Message},
    │ │ │ │               ok
    │ │ │ │  end.
    │ │ │ │  
    │ │ │ │  
    │ │ │ │  %%% The client process which runs on each server node
    │ │ │ │ -client(Server_Node, Name) ->
    │ │ │ │ -    {messenger, Server_Node} ! {self(), logon, Name},
    │ │ │ │ -    await_result(),
    │ │ │ │ -    client(Server_Node).
    │ │ │ │ +client(Server_Node, Name) ->
    │ │ │ │ +    {messenger, Server_Node} ! {self(), logon, Name},
    │ │ │ │ +    await_result(),
    │ │ │ │ +    client(Server_Node).
    │ │ │ │  
    │ │ │ │ -client(Server_Node) ->
    │ │ │ │ +client(Server_Node) ->
    │ │ │ │      receive
    │ │ │ │          logoff ->
    │ │ │ │ -            {messenger, Server_Node} ! {self(), logoff},
    │ │ │ │ -            exit(normal);
    │ │ │ │ -        {message_to, ToName, Message} ->
    │ │ │ │ -            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
    │ │ │ │ -            await_result();
    │ │ │ │ -        {message_from, FromName, Message} ->
    │ │ │ │ -            io:format("Message from ~p: ~p~n", [FromName, Message])
    │ │ │ │ +            {messenger, Server_Node} ! {self(), logoff},
    │ │ │ │ +            exit(normal);
    │ │ │ │ +        {message_to, ToName, Message} ->
    │ │ │ │ +            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
    │ │ │ │ +            await_result();
    │ │ │ │ +        {message_from, FromName, Message} ->
    │ │ │ │ +            io:format("Message from ~p: ~p~n", [FromName, Message])
    │ │ │ │      end,
    │ │ │ │ -    client(Server_Node).
    │ │ │ │ +    client(Server_Node).
    │ │ │ │  
    │ │ │ │  %%% wait for a response from the server
    │ │ │ │ -await_result() ->
    │ │ │ │ +await_result() ->
    │ │ │ │      receive
    │ │ │ │ -        {messenger, stop, Why} -> % Stop the client
    │ │ │ │ -            io:format("~p~n", [Why]),
    │ │ │ │ -            exit(normal);
    │ │ │ │ -        {messenger, What} ->  % Normal response
    │ │ │ │ -            io:format("~p~n", [What])
    │ │ │ │ +        {messenger, stop, Why} -> % Stop the client
    │ │ │ │ +            io:format("~p~n", [Why]),
    │ │ │ │ +            exit(normal);
    │ │ │ │ +        {messenger, What} ->  % Normal response
    │ │ │ │ +            io:format("~p~n", [What])
    │ │ │ │      end.

    To use this program, you need to:

    • Configure the server_node() function.
    • Copy the compiled code (messenger.beam) to the directory on each computer │ │ │ │ where you start Erlang.

    In the following example using this program, nodes are started on four different │ │ │ │ computers. If you do not have that many machines available on your network, you │ │ │ │ can start several nodes on the same machine.

    Four Erlang nodes are started up: messenger@super, c1@bilbo, c2@kosken, │ │ │ │ -c3@gollum.

    First the server at messenger@super is started up:

    (messenger@super)1> messenger:start_server().
    │ │ │ │ -true

    Now Peter logs on at c1@bilbo:

    (c1@bilbo)1> messenger:logon(peter).
    │ │ │ │ +c3@gollum.

    First the server at messenger@super is started up:

    (messenger@super)1> messenger:start_server().
    │ │ │ │ +true

    Now Peter logs on at c1@bilbo:

    (c1@bilbo)1> messenger:logon(peter).
    │ │ │ │  true
    │ │ │ │ -logged_on

    James logs on at c2@kosken:

    (c2@kosken)1> messenger:logon(james).
    │ │ │ │ +logged_on

    James logs on at c2@kosken:

    (c2@kosken)1> messenger:logon(james).
    │ │ │ │  true
    │ │ │ │ -logged_on

    And Fred logs on at c3@gollum:

    (c3@gollum)1> messenger:logon(fred).
    │ │ │ │ +logged_on

    And Fred logs on at c3@gollum:

    (c3@gollum)1> messenger:logon(fred).
    │ │ │ │  true
    │ │ │ │ -logged_on

    Now Peter sends Fred a message:

    (c1@bilbo)2> messenger:message(fred, "hello").
    │ │ │ │ +logged_on

    Now Peter sends Fred a message:

    (c1@bilbo)2> messenger:message(fred, "hello").
    │ │ │ │  ok
    │ │ │ │  sent

    Fred receives the message and sends a message to Peter and logs off:

    Message from peter: "hello"
    │ │ │ │ -(c3@gollum)2> messenger:message(peter, "go away, I'm busy").
    │ │ │ │ +(c3@gollum)2> messenger:message(peter, "go away, I'm busy").
    │ │ │ │  ok
    │ │ │ │  sent
    │ │ │ │ -(c3@gollum)3> messenger:logoff().
    │ │ │ │ -logoff

    James now tries to send a message to Fred:

    (c2@kosken)2> messenger:message(fred, "peter doesn't like you").
    │ │ │ │ +(c3@gollum)3> messenger:logoff().
    │ │ │ │ +logoff

    James now tries to send a message to Fred:

    (c2@kosken)2> messenger:message(fred, "peter doesn't like you").
    │ │ │ │  ok
    │ │ │ │  receiver_not_found

    But this fails as Fred has already logged off.

    First let us look at some of the new concepts that have been introduced.

    There are two versions of the server_transfer function: one with four │ │ │ │ arguments (server_transfer/4) and one with five (server_transfer/5). These │ │ │ │ are regarded by Erlang as two separate functions.

    Notice how to write the server function so that it calls itself, through │ │ │ │ server(User_List), and thus creates a loop. The Erlang compiler is "clever" │ │ │ │ and optimizes the code so that this really is a sort of loop and not a proper │ │ │ │ function call. But this only works if there is no code after the call. │ │ │ │ Otherwise, the compiler expects the call to return and make a proper function │ │ │ │ call. This would result in the process getting bigger and bigger for every loop.

    Functions in the lists module are used. This is a very useful module and a │ │ │ │ study of the manual page is recommended (erl -man lists). │ │ │ │ lists:keymember(Key,Position,Lists) looks through a list of tuples and looks │ │ │ │ at Position in each tuple to see if it is the same as Key. The first element │ │ │ │ is position 1. If it finds a tuple where the element at Position is the same │ │ │ │ -as Key, it returns true, otherwise false.

    3> lists:keymember(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
    │ │ │ │ +as Key, it returns true, otherwise false.

    3> lists:keymember(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
    │ │ │ │  true
    │ │ │ │ -4> lists:keymember(p, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
    │ │ │ │ +4> lists:keymember(p, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
    │ │ │ │  false

    lists:keydelete works in the same way but deletes the first tuple found (if │ │ │ │ -any) and returns the remaining list:

    5> lists:keydelete(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
    │ │ │ │ -[{x,y,z},{b,b,b},{q,r,s}]

    lists:keysearch is like lists:keymember, but it returns │ │ │ │ +any) and returns the remaining list:

    5> lists:keydelete(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
    │ │ │ │ +[{x,y,z},{b,b,b},{q,r,s}]

    lists:keysearch is like lists:keymember, but it returns │ │ │ │ {value,Tuple_Found} or the atom false.

    There are many very useful functions in the lists module.

    An Erlang process (conceptually) runs until it does a receive and there is no │ │ │ │ message which it wants to receive in the message queue. "conceptually" is used │ │ │ │ here because the Erlang system shares the CPU time between the active processes │ │ │ │ in the system.

    A process terminates when there is nothing more for it to do, that is, the last │ │ │ │ function it calls simply returns and does not call another function. Another way │ │ │ │ for a process to terminate is for it to call exit/1. The argument │ │ │ │ to exit/1 has a special meaning, which is discussed later. In this │ │ │ │ example, exit(normal) is done, which has the same effect as a │ │ │ │ process running out of functions to call.

    The BIF whereis(RegisteredName) checks if a registered process │ │ │ │ of name RegisteredName exists. If it exists, the pid of that process is │ │ │ │ returned. If it does not exist, the atom undefined is returned.

    You should by now be able to understand most of the code in the │ │ │ │ messenger-module. Let us study one case in detail: a message is sent from one │ │ │ │ -user to another.

    The first user "sends" the message in the example above by:

    messenger:message(fred, "hello")

    After testing that the client process exists:

    whereis(mess_client)

    And a message is sent to mess_client:

    mess_client ! {message_to, fred, "hello"}

    The client sends the message to the server by:

    {messenger, messenger@super} ! {self(), message_to, fred, "hello"},

    And waits for a reply from the server.

    The server receives this message and calls:

    server_transfer(From, fred, "hello", User_List),

    This checks that the pid From is in the User_List:

    lists:keysearch(From, 1, User_List)

    If keysearch returns the atom false, some error has occurred and the server │ │ │ │ -sends back the message:

    From ! {messenger, stop, you_are_not_logged_on}

    This is received by the client, which in turn does exit(normal) │ │ │ │ +user to another.

    The first user "sends" the message in the example above by:

    messenger:message(fred, "hello")

    After testing that the client process exists:

    whereis(mess_client)

    And a message is sent to mess_client:

    mess_client ! {message_to, fred, "hello"}

    The client sends the message to the server by:

    {messenger, messenger@super} ! {self(), message_to, fred, "hello"},

    And waits for a reply from the server.

    The server receives this message and calls:

    server_transfer(From, fred, "hello", User_List),

    This checks that the pid From is in the User_List:

    lists:keysearch(From, 1, User_List)

    If keysearch returns the atom false, some error has occurred and the server │ │ │ │ +sends back the message:

    From ! {messenger, stop, you_are_not_logged_on}

    This is received by the client, which in turn does exit(normal) │ │ │ │ and terminates. If keysearch returns {value,{From,Name}} it is certain that │ │ │ │ -the user is logged on and that his name (peter) is in variable Name.

    Let us now call:

    server_transfer(From, peter, fred, "hello", User_List)

    Notice that as this is server_transfer/5, it is not the same as the previous │ │ │ │ +the user is logged on and that his name (peter) is in variable Name.

    Let us now call:

    server_transfer(From, peter, fred, "hello", User_List)

    Notice that as this is server_transfer/5, it is not the same as the previous │ │ │ │ function server_transfer/4. Another keysearch is done on User_List to find │ │ │ │ -the pid of the client corresponding to fred:

    lists:keysearch(fred, 2, User_List)

    This time argument 2 is used, which is the second element in the tuple. If this │ │ │ │ +the pid of the client corresponding to fred:

    lists:keysearch(fred, 2, User_List)

    This time argument 2 is used, which is the second element in the tuple. If this │ │ │ │ returns the atom false, fred is not logged on and the following message is │ │ │ │ -sent:

    From ! {messenger, receiver_not_found};

    This is received by the client.

    If keysearch returns:

    {value, {ToPid, fred}}

    The following message is sent to fred's client:

    ToPid ! {message_from, peter, "hello"},

    The following message is sent to peter's client:

    From ! {messenger, sent}

    Fred's client receives the message and prints it:

    {message_from, peter, "hello"} ->
    │ │ │ │ -    io:format("Message from ~p: ~p~n", [peter, "hello"])

    Peter's client receives the message in the await_result function.

    │ │ │ │ +sent:

    From ! {messenger, receiver_not_found};

    This is received by the client.

    If keysearch returns:

    {value, {ToPid, fred}}

    The following message is sent to fred's client:

    ToPid ! {message_from, peter, "hello"},

    The following message is sent to peter's client:

    From ! {messenger, sent}

    Fred's client receives the message and prints it:

    {message_from, peter, "hello"} ->
    │ │ │ │ +    io:format("Message from ~p: ~p~n", [peter, "hello"])

    Peter's client receives the message in the await_result function.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/commoncaveats.xhtml │ │ │ │ @@ -23,31 +23,31 @@ │ │ │ │

    This section lists a few constructs to watch out for.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Operator ++ │ │ │ │

    │ │ │ │

    The ++ operator copies its left-hand side operand. That is clearly │ │ │ │ -seen if we do our own implementation in Erlang:

    my_plus_plus([H|T], Tail) ->
    │ │ │ │ -    [H|my_plus_plus(T, Tail)];
    │ │ │ │ -my_plus_plus([], Tail) ->
    │ │ │ │ -    Tail.

    We must be careful how we use ++ in a loop. First is how not to use it:

    DO NOT

    naive_reverse([H|T]) ->
    │ │ │ │ -    naive_reverse(T) ++ [H];
    │ │ │ │ -naive_reverse([]) ->
    │ │ │ │ -    [].

    As the ++ operator copies its left-hand side operand, the growing │ │ │ │ -result is copied repeatedly, leading to quadratic complexity.

    On the other hand, using ++ in loop like this is perfectly fine:

    OK

    naive_but_ok_reverse(List) ->
    │ │ │ │ -    naive_but_ok_reverse(List, []).
    │ │ │ │ +seen if we do our own implementation in Erlang:

    my_plus_plus([H|T], Tail) ->
    │ │ │ │ +    [H|my_plus_plus(T, Tail)];
    │ │ │ │ +my_plus_plus([], Tail) ->
    │ │ │ │ +    Tail.

    We must be careful how we use ++ in a loop. First is how not to use it:

    DO NOT

    naive_reverse([H|T]) ->
    │ │ │ │ +    naive_reverse(T) ++ [H];
    │ │ │ │ +naive_reverse([]) ->
    │ │ │ │ +    [].

    As the ++ operator copies its left-hand side operand, the growing │ │ │ │ +result is copied repeatedly, leading to quadratic complexity.

    On the other hand, using ++ in loop like this is perfectly fine:

    OK

    naive_but_ok_reverse(List) ->
    │ │ │ │ +    naive_but_ok_reverse(List, []).
    │ │ │ │  
    │ │ │ │ -naive_but_ok_reverse([H|T], Acc) ->
    │ │ │ │ -    naive_but_ok_reverse(T, [H] ++ Acc);
    │ │ │ │ -naive_but_ok_reverse([], Acc) ->
    │ │ │ │ +naive_but_ok_reverse([H|T], Acc) ->
    │ │ │ │ +    naive_but_ok_reverse(T, [H] ++ Acc);
    │ │ │ │ +naive_but_ok_reverse([], Acc) ->
    │ │ │ │      Acc.

    Each list element is copied only once. The growing result Acc is the right-hand │ │ │ │ -side operand, which it is not copied.

    Experienced Erlang programmers would probably write as follows:

    DO

    vanilla_reverse([H|T], Acc) ->
    │ │ │ │ -    vanilla_reverse(T, [H|Acc]);
    │ │ │ │ -vanilla_reverse([], Acc) ->
    │ │ │ │ +side operand, which it is not copied.

    Experienced Erlang programmers would probably write as follows:

    DO

    vanilla_reverse([H|T], Acc) ->
    │ │ │ │ +    vanilla_reverse(T, [H|Acc]);
    │ │ │ │ +vanilla_reverse([], Acc) ->
    │ │ │ │      Acc.

    In principle, this is slightly more efficient because the list element [H] │ │ │ │ is not built before being copied and discarded. In practice, the compiler │ │ │ │ rewrites [H] ++ Acc to [H|Acc].

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Timer Module │ │ │ │ @@ -65,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=Shared}.

    In the process that calls init2/0, the size of the data field in the state │ │ │ │ +of a term with a shared subterm:

    {SubTerm, SubTerm}

    When a term is copied to another process, sharing of subterms will be lost and │ │ │ │ +the copied term can be many times larger than the original term. For example:

    init2() ->
    │ │ │ │ +    SharedSubTerms = lists:foldl(fun(_, A) -> [A|A] end, [0], lists:seq(1, 15)),
    │ │ │ │ +    #state{data=Shared}.

    In the process that calls init2/0, the size of the data field in the state │ │ │ │ record will be 32 heap words. When the record is copied to the newly created │ │ │ │ process, sharing will be lost and the size of the copied data field will be │ │ │ │ 131070 heap words. More details about │ │ │ │ loss off sharing are found in a later │ │ │ │ section.

    To avoid the problem, outside of the fun extract only the fields of the record │ │ │ │ -that are actually used:

    DO

    fixed_accidental1(State) ->
    │ │ │ │ +that are actually used:

    DO

    fixed_accidental1(State) ->
    │ │ │ │      Info = State#state.info,
    │ │ │ │ -    spawn(fun() ->
    │ │ │ │ -                  io:format("~p\n", [Info])
    │ │ │ │ -          end).

    Similarly, outside of the fun extract only the map elements that are actually │ │ │ │ -used:

    DO

    fixed_accidental2(State) ->
    │ │ │ │ -    Info = map_get(info, State),
    │ │ │ │ -    spawn(fun() ->
    │ │ │ │ -                  io:format("~p\n", [Info])
    │ │ │ │ -          end).

    │ │ │ │ + spawn(fun() -> │ │ │ │ + io:format("~p\n", [Info]) │ │ │ │ + end).

    Similarly, outside of the fun extract only the map elements that are actually │ │ │ │ +used:

    DO

    fixed_accidental2(State) ->
    │ │ │ │ +    Info = map_get(info, State),
    │ │ │ │ +    spawn(fun() ->
    │ │ │ │ +                  io:format("~p\n", [Info])
    │ │ │ │ +          end).

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ list_to_atom/1, 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, therefore the object files get the suffix │ │ │ │ -.beam. The compiler can also generate a binary which can be loaded directly.

    The compiler is located in the module compile in Compiler.

    compile:file(Module)
    │ │ │ │ -compile:file(Module, Options)

    The Erlang shell understands the command c(Module), which both compiles and │ │ │ │ +.beam. The compiler can also generate a binary which can be loaded directly.

    The compiler is located in the module compile in Compiler.

    compile:file(Module)
    │ │ │ │ +compile:file(Module, Options)

    The Erlang shell understands the command c(Module), which both compiles and │ │ │ │ loads Module.

    There is also a module make, which provides a set of functions similar to the │ │ │ │ UNIX type Make functions, see module make in Tools.

    The compiler can also be accessed from the OS prompt using the │ │ │ │ erl executable in ERTS.

    % erl -compile Module1...ModuleN
    │ │ │ │  % erl -make

    The erlc program provides way to compile modules from the OS │ │ │ │ shell, see the erlc executable in ERTS. It │ │ │ │ understands a number of flags that can be used to define macros, add search │ │ │ │ paths for include files, and more.

    % erlc <flags> File1.erl...FileN.erl

    │ │ │ │ @@ -61,51 +61,51 @@ │ │ │ │ When a module is loaded into the system for the first time, the code becomes │ │ │ │ 'current'. If then a new instance of the module is loaded, the code of the │ │ │ │ previous instance becomes 'old' and the new instance becomes 'current'.

    Both old and current code is valid, and can be evaluated concurrently. Fully │ │ │ │ qualified function calls always refer to current code. Old code can still be │ │ │ │ evaluated because of processes lingering in the old code.

    If a third instance of the module is loaded, the code server removes (purges) │ │ │ │ the old code and any processes lingering in it is terminated. Then the third │ │ │ │ instance becomes 'current' and the previously current code becomes 'old'.

    To change from old code to current code, a process must make a fully qualified │ │ │ │ -function call.

    Example:

    -module(m).
    │ │ │ │ --export([loop/0]).
    │ │ │ │ +function call.

    Example:

    -module(m).
    │ │ │ │ +-export([loop/0]).
    │ │ │ │  
    │ │ │ │ -loop() ->
    │ │ │ │ +loop() ->
    │ │ │ │      receive
    │ │ │ │          code_switch ->
    │ │ │ │ -            m:loop();
    │ │ │ │ +            m:loop();
    │ │ │ │          Msg ->
    │ │ │ │              ...
    │ │ │ │ -            loop()
    │ │ │ │ +            loop()
    │ │ │ │      end.

    To make the process change code, send the message code_switch to it. The │ │ │ │ process then makes a fully qualified call to m:loop() and changes to current │ │ │ │ code. Notice that m:loop/0 must be exported.

    For code replacement of funs to work, use the syntax │ │ │ │ fun Module:FunctionName/Arity.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Running a Function When a Module is Loaded │ │ │ │

    │ │ │ │

    The -on_load() directive names a function that is to be run automatically when │ │ │ │ -a module is loaded.

    Its syntax is as follows:

    -on_load(Name/0).

    It is not necessary to export the function. It is called in a freshly spawned │ │ │ │ +a module is loaded.

    Its syntax is as follows:

    -on_load(Name/0).

    It is not necessary to export the function. It is called in a freshly spawned │ │ │ │ process (which terminates as soon as the function returns).

    The function must return ok if the module is to become the new current code │ │ │ │ for the module and become callable.

    Returning any other value or generating an exception causes the new code to be │ │ │ │ unloaded. If the return value is not an atom, a warning error report is sent to │ │ │ │ the error logger.

    If there already is current code for the module, that code will remain current │ │ │ │ and can be called until the on_load function has returned. If the on_load │ │ │ │ function fails, the current code (if any) will remain current. If there is no │ │ │ │ current code for a module, any process that makes an external call to the module │ │ │ │ before the on_load function has finished will be suspended until the on_load │ │ │ │ function have finished.

    Change

    Before Erlang/OTP 19, if the on_load function failed, any previously current │ │ │ │ code would become old, essentially leaving the system without any working and │ │ │ │ reachable instance of the module.

    In embedded mode, first all modules are loaded. Then all on_load functions are │ │ │ │ called. The system is terminated unless all of the on_load functions return │ │ │ │ -ok.

    Example:

    -module(m).
    │ │ │ │ --on_load(load_my_nifs/0).
    │ │ │ │ +ok.

    Example:

    -module(m).
    │ │ │ │ +-on_load(load_my_nifs/0).
    │ │ │ │  
    │ │ │ │ -load_my_nifs() ->
    │ │ │ │ +load_my_nifs() ->
    │ │ │ │      NifPath = ...,    %Set up the path to the NIF library.
    │ │ │ │      Info = ...,       %Initialize the Info term
    │ │ │ │ -    erlang:load_nif(NifPath, Info).

    If the call to erlang:load_nif/2 fails, the module is unloaded and a warning │ │ │ │ + erlang:load_nif(NifPath, Info).

    If the call to erlang:load_nif/2 fails, the module is unloaded and a warning │ │ │ │ report is sent to the error loader.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/c_portdriver.xhtml │ │ │ │ @@ -56,112 +56,112 @@ │ │ │ │

    Like a port program, the port communicates with an Erlang process. All │ │ │ │ communication goes through one Erlang process that is the connected process of │ │ │ │ the port driver. Terminating this process closes the port driver.

    Before the port is created, the driver must be loaded. This is done with the │ │ │ │ function erl_ddll:load_driver/2, with the name of the shared library as │ │ │ │ argument.

    The port is then created using the BIF open_port/2, with the │ │ │ │ tuple {spawn, DriverName} as the first argument. The string SharedLib is the │ │ │ │ name of the port driver. The second argument is a list of options, none in this │ │ │ │ -case:

    -module(complex5).
    │ │ │ │ --export([start/1, init/1]).
    │ │ │ │ +case:

    -module(complex5).
    │ │ │ │ +-export([start/1, init/1]).
    │ │ │ │  
    │ │ │ │ -start(SharedLib) ->
    │ │ │ │ -    case erl_ddll:load_driver(".", SharedLib) of
    │ │ │ │ +start(SharedLib) ->
    │ │ │ │ +    case erl_ddll:load_driver(".", SharedLib) of
    │ │ │ │          ok -> ok;
    │ │ │ │ -        {error, already_loaded} -> ok;
    │ │ │ │ -        _ -> exit({error, could_not_load_driver})
    │ │ │ │ +        {error, already_loaded} -> ok;
    │ │ │ │ +        _ -> exit({error, could_not_load_driver})
    │ │ │ │      end,
    │ │ │ │ -    spawn(?MODULE, init, [SharedLib]).
    │ │ │ │ +    spawn(?MODULE, init, [SharedLib]).
    │ │ │ │  
    │ │ │ │ -init(SharedLib) ->
    │ │ │ │ -  register(complex, self()),
    │ │ │ │ -  Port = open_port({spawn, SharedLib}, []),
    │ │ │ │ -  loop(Port).

    Now complex5:foo/1 and complex5:bar/1 can be implemented. Both send a │ │ │ │ -message to the complex process and receive the following reply:

    foo(X) ->
    │ │ │ │ -    call_port({foo, X}).
    │ │ │ │ -bar(Y) ->
    │ │ │ │ -    call_port({bar, Y}).
    │ │ │ │ +init(SharedLib) ->
    │ │ │ │ +  register(complex, self()),
    │ │ │ │ +  Port = open_port({spawn, SharedLib}, []),
    │ │ │ │ +  loop(Port).

    Now complex5:foo/1 and complex5:bar/1 can be implemented. Both send a │ │ │ │ +message to the complex process and receive the following reply:

    foo(X) ->
    │ │ │ │ +    call_port({foo, X}).
    │ │ │ │ +bar(Y) ->
    │ │ │ │ +    call_port({bar, Y}).
    │ │ │ │  
    │ │ │ │ -call_port(Msg) ->
    │ │ │ │ -    complex ! {call, self(), Msg},
    │ │ │ │ +call_port(Msg) ->
    │ │ │ │ +    complex ! {call, self(), Msg},
    │ │ │ │      receive
    │ │ │ │ -        {complex, Result} ->
    │ │ │ │ +        {complex, Result} ->
    │ │ │ │              Result
    │ │ │ │ -    end.

    The complex process performs the following:

    • Encodes the message into a sequence of bytes.
    • Sends it to the port.
    • Waits for a reply.
    • Decodes the reply.
    • Sends it back to the caller:
    loop(Port) ->
    │ │ │ │ +    end.

    The complex process performs the following:

    • Encodes the message into a sequence of bytes.
    • Sends it to the port.
    • Waits for a reply.
    • Decodes the reply.
    • Sends it back to the caller:
    loop(Port) ->
    │ │ │ │      receive
    │ │ │ │ -        {call, Caller, Msg} ->
    │ │ │ │ -            Port ! {self(), {command, encode(Msg)}},
    │ │ │ │ +        {call, Caller, Msg} ->
    │ │ │ │ +            Port ! {self(), {command, encode(Msg)}},
    │ │ │ │              receive
    │ │ │ │ -                {Port, {data, Data}} ->
    │ │ │ │ -                    Caller ! {complex, decode(Data)}
    │ │ │ │ +                {Port, {data, Data}} ->
    │ │ │ │ +                    Caller ! {complex, decode(Data)}
    │ │ │ │              end,
    │ │ │ │ -            loop(Port)
    │ │ │ │ +            loop(Port)
    │ │ │ │      end.

    Assuming that both the arguments and the results from the C functions are less │ │ │ │ than 256, a simple encoding/decoding scheme is employed. In this scheme, foo │ │ │ │ is represented by byte 1, bar is represented by 2, and the argument/result is │ │ │ │ -represented by a single byte as well:

    encode({foo, X}) -> [1, X];
    │ │ │ │ -encode({bar, Y}) -> [2, Y].
    │ │ │ │ +represented by a single byte as well:

    encode({foo, X}) -> [1, X];
    │ │ │ │ +encode({bar, Y}) -> [2, Y].
    │ │ │ │  
    │ │ │ │ -decode([Int]) -> Int.

    The resulting Erlang program, including functions for stopping the port and │ │ │ │ +decode([Int]) -> Int.

    The resulting Erlang program, including functions for stopping the port and │ │ │ │ detecting port failures, is as follows:

    
    │ │ │ │ --module(complex5).
    │ │ │ │ --export([start/1, stop/0, init/1]).
    │ │ │ │ --export([foo/1, bar/1]).
    │ │ │ │ +-module(complex5).
    │ │ │ │ +-export([start/1, stop/0, init/1]).
    │ │ │ │ +-export([foo/1, bar/1]).
    │ │ │ │  
    │ │ │ │ -start(SharedLib) ->
    │ │ │ │ -    case erl_ddll:load_driver(".", SharedLib) of
    │ │ │ │ +start(SharedLib) ->
    │ │ │ │ +    case erl_ddll:load_driver(".", SharedLib) of
    │ │ │ │  	ok -> ok;
    │ │ │ │ -	{error, already_loaded} -> ok;
    │ │ │ │ -	_ -> exit({error, could_not_load_driver})
    │ │ │ │ +	{error, already_loaded} -> ok;
    │ │ │ │ +	_ -> exit({error, could_not_load_driver})
    │ │ │ │      end,
    │ │ │ │ -    spawn(?MODULE, init, [SharedLib]).
    │ │ │ │ +    spawn(?MODULE, init, [SharedLib]).
    │ │ │ │  
    │ │ │ │ -init(SharedLib) ->
    │ │ │ │ -    register(complex, self()),
    │ │ │ │ -    Port = open_port({spawn, SharedLib}, []),
    │ │ │ │ -    loop(Port).
    │ │ │ │ +init(SharedLib) ->
    │ │ │ │ +    register(complex, self()),
    │ │ │ │ +    Port = open_port({spawn, SharedLib}, []),
    │ │ │ │ +    loop(Port).
    │ │ │ │  
    │ │ │ │ -stop() ->
    │ │ │ │ +stop() ->
    │ │ │ │      complex ! stop.
    │ │ │ │  
    │ │ │ │ -foo(X) ->
    │ │ │ │ -    call_port({foo, X}).
    │ │ │ │ -bar(Y) ->
    │ │ │ │ -    call_port({bar, Y}).
    │ │ │ │ +foo(X) ->
    │ │ │ │ +    call_port({foo, X}).
    │ │ │ │ +bar(Y) ->
    │ │ │ │ +    call_port({bar, Y}).
    │ │ │ │  
    │ │ │ │ -call_port(Msg) ->
    │ │ │ │ -    complex ! {call, self(), Msg},
    │ │ │ │ +call_port(Msg) ->
    │ │ │ │ +    complex ! {call, self(), Msg},
    │ │ │ │      receive
    │ │ │ │ -	{complex, Result} ->
    │ │ │ │ +	{complex, Result} ->
    │ │ │ │  	    Result
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -loop(Port) ->
    │ │ │ │ +loop(Port) ->
    │ │ │ │      receive
    │ │ │ │ -	{call, Caller, Msg} ->
    │ │ │ │ -	    Port ! {self(), {command, encode(Msg)}},
    │ │ │ │ +	{call, Caller, Msg} ->
    │ │ │ │ +	    Port ! {self(), {command, encode(Msg)}},
    │ │ │ │  	    receive
    │ │ │ │ -		{Port, {data, Data}} ->
    │ │ │ │ -		    Caller ! {complex, decode(Data)}
    │ │ │ │ +		{Port, {data, Data}} ->
    │ │ │ │ +		    Caller ! {complex, decode(Data)}
    │ │ │ │  	    end,
    │ │ │ │ -	    loop(Port);
    │ │ │ │ +	    loop(Port);
    │ │ │ │  	stop ->
    │ │ │ │ -	    Port ! {self(), close},
    │ │ │ │ +	    Port ! {self(), close},
    │ │ │ │  	    receive
    │ │ │ │ -		{Port, closed} ->
    │ │ │ │ -		    exit(normal)
    │ │ │ │ +		{Port, closed} ->
    │ │ │ │ +		    exit(normal)
    │ │ │ │  	    end;
    │ │ │ │ -	{'EXIT', Port, Reason} ->
    │ │ │ │ -	    io:format("~p ~n", [Reason]),
    │ │ │ │ -	    exit(port_terminated)
    │ │ │ │ +	{'EXIT', Port, Reason} ->
    │ │ │ │ +	    io:format("~p ~n", [Reason]),
    │ │ │ │ +	    exit(port_terminated)
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -encode({foo, X}) -> [1, X];
    │ │ │ │ -encode({bar, Y}) -> [2, Y].
    │ │ │ │ +encode({foo, X}) -> [1, X];
    │ │ │ │ +encode({bar, Y}) -> [2, Y].
    │ │ │ │  
    │ │ │ │ -decode([Int]) -> Int.

    │ │ │ │ +decode([Int]) -> Int.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ C Driver │ │ │ │

    │ │ │ │

    The C driver is a module that is compiled and linked into a shared library. It │ │ │ │ uses a driver structure and includes the header file erl_driver.h.

    The driver structure is filled with the driver name and function pointers. It is │ │ │ │ @@ -252,22 +252,22 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Running the Example │ │ │ │

    │ │ │ │

    Step 1. Compile the C code:

    unix> gcc -o example_drv.so -fpic -shared complex.c port_driver.c
    │ │ │ │  windows> cl -LD -MD -Fe example_drv.dll complex.c port_driver.c

    Step 2. Start Erlang and compile the Erlang code:

    > erl
    │ │ │ │ -Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
    │ │ │ │ +Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
    │ │ │ │  
    │ │ │ │ -Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
    │ │ │ │ -1> c(complex5).
    │ │ │ │ -{ok,complex5}

    Step 3. Run the example:

    2> complex5:start("example_drv").
    │ │ │ │ +Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
    │ │ │ │ +1> c(complex5).
    │ │ │ │ +{ok,complex5}

    Step 3. Run the example:

    2> complex5:start("example_drv").
    │ │ │ │  <0.34.0>
    │ │ │ │ -3> complex5:foo(3).
    │ │ │ │ +3> complex5:foo(3).
    │ │ │ │  4
    │ │ │ │ -4> complex5:bar(5).
    │ │ │ │ +4> complex5:bar(5).
    │ │ │ │  10
    │ │ │ │ -5> complex5:stop().
    │ │ │ │ +5> complex5:stop().
    │ │ │ │  stop
    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/c_port.xhtml │ │ │ │ @@ -53,101 +53,101 @@ │ │ │ │ external program, if it is written properly).

    The port is created using the BIF open_port/2 with │ │ │ │ {spawn,ExtPrg} as the first argument. The string ExtPrg is the name of the │ │ │ │ external program, including any command line arguments. The second argument is a │ │ │ │ list of options, in this case only {packet,2}. This option says that a 2 byte │ │ │ │ length indicator is to be used to simplify the communication between C and │ │ │ │ Erlang. The Erlang port automatically adds the length indicator, but this must │ │ │ │ be done explicitly in the external C program.

    The process is also set to trap exits, which enables detection of failure of the │ │ │ │ -external program:

    -module(complex1).
    │ │ │ │ --export([start/1, init/1]).
    │ │ │ │ +external program:

    -module(complex1).
    │ │ │ │ +-export([start/1, init/1]).
    │ │ │ │  
    │ │ │ │ -start(ExtPrg) ->
    │ │ │ │ -  spawn(?MODULE, init, [ExtPrg]).
    │ │ │ │ +start(ExtPrg) ->
    │ │ │ │ +  spawn(?MODULE, init, [ExtPrg]).
    │ │ │ │  
    │ │ │ │ -init(ExtPrg) ->
    │ │ │ │ -  register(complex, self()),
    │ │ │ │ -  process_flag(trap_exit, true),
    │ │ │ │ -  Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
    │ │ │ │ -  loop(Port).

    Now complex1:foo/1 and complex1:bar/1 can be implemented. Both send a │ │ │ │ -message to the complex process and receive the following replies:

    foo(X) ->
    │ │ │ │ -  call_port({foo, X}).
    │ │ │ │ -bar(Y) ->
    │ │ │ │ -  call_port({bar, Y}).
    │ │ │ │ +init(ExtPrg) ->
    │ │ │ │ +  register(complex, self()),
    │ │ │ │ +  process_flag(trap_exit, true),
    │ │ │ │ +  Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
    │ │ │ │ +  loop(Port).

    Now complex1:foo/1 and complex1:bar/1 can be implemented. Both send a │ │ │ │ +message to the complex process and receive the following replies:

    foo(X) ->
    │ │ │ │ +  call_port({foo, X}).
    │ │ │ │ +bar(Y) ->
    │ │ │ │ +  call_port({bar, Y}).
    │ │ │ │  
    │ │ │ │ -call_port(Msg) ->
    │ │ │ │ -  complex ! {call, self(), Msg},
    │ │ │ │ +call_port(Msg) ->
    │ │ │ │ +  complex ! {call, self(), Msg},
    │ │ │ │    receive
    │ │ │ │ -    {complex, Result} ->
    │ │ │ │ +    {complex, Result} ->
    │ │ │ │        Result
    │ │ │ │ -  end.

    The complex process does the following:

    • Encodes the message into a sequence of bytes.
    • Sends it to the port.
    • Waits for a reply.
    • Decodes the reply.
    • Sends it back to the caller:
    loop(Port) ->
    │ │ │ │ +  end.

    The complex process does the following:

    • Encodes the message into a sequence of bytes.
    • Sends it to the port.
    • Waits for a reply.
    • Decodes the reply.
    • Sends it back to the caller:
    loop(Port) ->
    │ │ │ │    receive
    │ │ │ │ -    {call, Caller, Msg} ->
    │ │ │ │ -      Port ! {self(), {command, encode(Msg)}},
    │ │ │ │ +    {call, Caller, Msg} ->
    │ │ │ │ +      Port ! {self(), {command, encode(Msg)}},
    │ │ │ │        receive
    │ │ │ │ -        {Port, {data, Data}} ->
    │ │ │ │ -          Caller ! {complex, decode(Data)}
    │ │ │ │ +        {Port, {data, Data}} ->
    │ │ │ │ +          Caller ! {complex, decode(Data)}
    │ │ │ │        end,
    │ │ │ │ -      loop(Port)
    │ │ │ │ +      loop(Port)
    │ │ │ │    end.

    Assuming that both the arguments and the results from the C functions are less │ │ │ │ than 256, a simple encoding/decoding scheme is employed. In this scheme, foo │ │ │ │ is represented by byte 1, bar is represented by 2, and the argument/result is │ │ │ │ -represented by a single byte as well:

    encode({foo, X}) -> [1, X];
    │ │ │ │ -encode({bar, Y}) -> [2, Y].
    │ │ │ │ +represented by a single byte as well:

    encode({foo, X}) -> [1, X];
    │ │ │ │ +encode({bar, Y}) -> [2, Y].
    │ │ │ │  
    │ │ │ │ -decode([Int]) -> Int.

    The resulting Erlang program, including functionality for stopping the port and │ │ │ │ -detecting port failures, is as follows:

    -module(complex1).
    │ │ │ │ --export([start/1, stop/0, init/1]).
    │ │ │ │ --export([foo/1, bar/1]).
    │ │ │ │ -
    │ │ │ │ -start(ExtPrg) ->
    │ │ │ │ -    spawn(?MODULE, init, [ExtPrg]).
    │ │ │ │ -stop() ->
    │ │ │ │ +decode([Int]) -> Int.

    The resulting Erlang program, including functionality for stopping the port and │ │ │ │ +detecting port failures, is as follows:

    -module(complex1).
    │ │ │ │ +-export([start/1, stop/0, init/1]).
    │ │ │ │ +-export([foo/1, bar/1]).
    │ │ │ │ +
    │ │ │ │ +start(ExtPrg) ->
    │ │ │ │ +    spawn(?MODULE, init, [ExtPrg]).
    │ │ │ │ +stop() ->
    │ │ │ │      complex ! stop.
    │ │ │ │  
    │ │ │ │ -foo(X) ->
    │ │ │ │ -    call_port({foo, X}).
    │ │ │ │ -bar(Y) ->
    │ │ │ │ -    call_port({bar, Y}).
    │ │ │ │ +foo(X) ->
    │ │ │ │ +    call_port({foo, X}).
    │ │ │ │ +bar(Y) ->
    │ │ │ │ +    call_port({bar, Y}).
    │ │ │ │  
    │ │ │ │ -call_port(Msg) ->
    │ │ │ │ -    complex ! {call, self(), Msg},
    │ │ │ │ +call_port(Msg) ->
    │ │ │ │ +    complex ! {call, self(), Msg},
    │ │ │ │      receive
    │ │ │ │ -	{complex, Result} ->
    │ │ │ │ +	{complex, Result} ->
    │ │ │ │  	    Result
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -init(ExtPrg) ->
    │ │ │ │ -    register(complex, self()),
    │ │ │ │ -    process_flag(trap_exit, true),
    │ │ │ │ -    Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
    │ │ │ │ -    loop(Port).
    │ │ │ │ +init(ExtPrg) ->
    │ │ │ │ +    register(complex, self()),
    │ │ │ │ +    process_flag(trap_exit, true),
    │ │ │ │ +    Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
    │ │ │ │ +    loop(Port).
    │ │ │ │  
    │ │ │ │ -loop(Port) ->
    │ │ │ │ +loop(Port) ->
    │ │ │ │      receive
    │ │ │ │ -	{call, Caller, Msg} ->
    │ │ │ │ -	    Port ! {self(), {command, encode(Msg)}},
    │ │ │ │ +	{call, Caller, Msg} ->
    │ │ │ │ +	    Port ! {self(), {command, encode(Msg)}},
    │ │ │ │  	    receive
    │ │ │ │ -		{Port, {data, Data}} ->
    │ │ │ │ -		    Caller ! {complex, decode(Data)}
    │ │ │ │ +		{Port, {data, Data}} ->
    │ │ │ │ +		    Caller ! {complex, decode(Data)}
    │ │ │ │  	    end,
    │ │ │ │ -	    loop(Port);
    │ │ │ │ +	    loop(Port);
    │ │ │ │  	stop ->
    │ │ │ │ -	    Port ! {self(), close},
    │ │ │ │ +	    Port ! {self(), close},
    │ │ │ │  	    receive
    │ │ │ │ -		{Port, closed} ->
    │ │ │ │ -		    exit(normal)
    │ │ │ │ +		{Port, closed} ->
    │ │ │ │ +		    exit(normal)
    │ │ │ │  	    end;
    │ │ │ │ -	{'EXIT', Port, Reason} ->
    │ │ │ │ -	    exit(port_terminated)
    │ │ │ │ +	{'EXIT', Port, Reason} ->
    │ │ │ │ +	    exit(port_terminated)
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -encode({foo, X}) -> [1, X];
    │ │ │ │ -encode({bar, Y}) -> [2, Y].
    │ │ │ │ +encode({foo, X}) -> [1, X];
    │ │ │ │ +encode({bar, Y}) -> [2, Y].
    │ │ │ │  
    │ │ │ │ -decode([Int]) -> Int.

    │ │ │ │ +decode([Int]) -> Int.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ C Program │ │ │ │

    │ │ │ │

    On the C side, it is necessary to write functions for receiving and sending data │ │ │ │ with 2 byte length indicators from/to Erlang. By default, the C program is to │ │ │ │ @@ -238,22 +238,22 @@ │ │ │ │ and terminates.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Running the Example │ │ │ │

    │ │ │ │

    Step 1. Compile the C code:

    $ gcc -o extprg complex.c erl_comm.c port.c

    Step 2. Start Erlang and compile the Erlang code:

    $ erl
    │ │ │ │ -Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
    │ │ │ │ +Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
    │ │ │ │  
    │ │ │ │ -Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
    │ │ │ │ -1> c(complex1).
    │ │ │ │ -{ok,complex1}

    Step 3. Run the example:

    2> complex1:start("./extprg").
    │ │ │ │ +Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
    │ │ │ │ +1> c(complex1).
    │ │ │ │ +{ok,complex1}

    Step 3. Run the example:

    2> complex1:start("./extprg").
    │ │ │ │  <0.34.0>
    │ │ │ │ -3> complex1:foo(3).
    │ │ │ │ +3> complex1:foo(3).
    │ │ │ │  4
    │ │ │ │ -4> complex1:bar(5).
    │ │ │ │ +4> complex1:bar(5).
    │ │ │ │  10
    │ │ │ │ -5> complex1:stop().
    │ │ │ │ +5> complex1:stop().
    │ │ │ │  stop
    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/bit_syntax.xhtml │ │ │ │ @@ -24,48 +24,48 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Introduction │ │ │ │ │ │ │ │

    The complete specification for the bit syntax appears in the │ │ │ │ Reference Manual.

    In Erlang, a Bin is used for constructing binaries and matching binary patterns. │ │ │ │ -A Bin is written with the following syntax:

    <<E1, E2, ... En>>

    A Bin is a low-level sequence of bits or bytes. The purpose of a Bin is to │ │ │ │ -enable construction of binaries:

    Bin = <<E1, E2, ... En>>

    All elements must be bound. Or match a binary:

    <<E1, E2, ... En>> = Bin

    Here, Bin is bound and the elements are bound or unbound, as in any match.

    A Bin does not need to consist of a whole number of bytes.

    A bitstring is a sequence of zero or more bits, where the number of bits does │ │ │ │ +A Bin is written with the following syntax:

    <<E1, E2, ... En>>

    A Bin is a low-level sequence of bits or bytes. The purpose of a Bin is to │ │ │ │ +enable construction of binaries:

    Bin = <<E1, E2, ... En>>

    All elements must be bound. Or match a binary:

    <<E1, E2, ... En>> = Bin

    Here, Bin is bound and the elements are bound or unbound, as in any match.

    A Bin does not need to consist of a whole number of bytes.

    A bitstring is a sequence of zero or more bits, where the number of bits does │ │ │ │ not need to be divisible by 8. If the number of bits is divisible by 8, the │ │ │ │ bitstring is also a binary.

    Each element specifies a certain segment of the bitstring. A segment is a set │ │ │ │ of contiguous bits of the binary (not necessarily on a byte boundary). The first │ │ │ │ element specifies the initial segment, the second element specifies the │ │ │ │ following segment, and so on.

    The following examples illustrate how binaries are constructed, or matched, and │ │ │ │ how elements and tails are specified.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │ │

    │ │ │ │

    Example 1: A binary can be constructed from a set of constants or a string │ │ │ │ -literal:

    Bin11 = <<1, 17, 42>>,
    │ │ │ │ -Bin12 = <<"abc">>

    This gives two binaries of size 3, with the following evaluations:

    Example 2:Similarly, a binary can be constructed from a set of bound │ │ │ │ +literal:

    Bin11 = <<1, 17, 42>>,
    │ │ │ │ +Bin12 = <<"abc">>

    This gives two binaries of size 3, with the following evaluations:

    Example 2:Similarly, a binary can be constructed from a set of bound │ │ │ │ variables:

    A = 1, B = 17, C = 42,
    │ │ │ │ -Bin2 = <<A, B, C:16>>

    This gives a binary of size 4. Here, a size expression is used for the │ │ │ │ +Bin2 = <<A, B, C:16>>

    This gives a binary of size 4. Here, a size expression is used for the │ │ │ │ variable C to specify a 16-bits segment of Bin2.

    binary_to_list(Bin2) evaluates to [1, 17, 00, 42].

    Example 3: A Bin can also be used for matching. D, E, and F are unbound │ │ │ │ -variables, and Bin2 is bound, as in Example 2:

    <<D:16, E, F/binary>> = Bin2

    This gives D = 273, E = 00, and F binds to a binary of size 1: │ │ │ │ +variables, and Bin2 is bound, as in Example 2:

    <<D:16, E, F/binary>> = Bin2

    This gives D = 273, E = 00, and F binds to a binary of size 1: │ │ │ │ binary_to_list(F) = [42].

    Example 4: The following is a more elaborate example of matching. Here, │ │ │ │ Dgram is bound to the consecutive bytes of an IP datagram of IP protocol │ │ │ │ -version 4. The ambition is to extract the header and the data of the datagram:

    -define(IP_VERSION, 4).
    │ │ │ │ --define(IP_MIN_HDR_LEN, 5).
    │ │ │ │ +version 4. The ambition is to extract the header and the data of the datagram:

    -define(IP_VERSION, 4).
    │ │ │ │ +-define(IP_MIN_HDR_LEN, 5).
    │ │ │ │  
    │ │ │ │ -DgramSize = byte_size(Dgram),
    │ │ │ │ +DgramSize = byte_size(Dgram),
    │ │ │ │  case Dgram of
    │ │ │ │ -    <<?IP_VERSION:4, HLen:4, SrvcType:8, TotLen:16,
    │ │ │ │ +    <<?IP_VERSION:4, HLen:4, SrvcType:8, TotLen:16,
    │ │ │ │        ID:16, Flgs:3, FragOff:13,
    │ │ │ │        TTL:8, Proto:8, HdrChkSum:16,
    │ │ │ │        SrcIP:32,
    │ │ │ │ -      DestIP:32, RestDgram/binary>> when HLen>=5, 4*HLen=<DgramSize ->
    │ │ │ │ -        OptsLen = 4*(HLen - ?IP_MIN_HDR_LEN),
    │ │ │ │ -        <<Opts:OptsLen/binary,Data/binary>> = RestDgram,
    │ │ │ │ +      DestIP:32, RestDgram/binary>> when HLen>=5, 4*HLen=<DgramSize ->
    │ │ │ │ +        OptsLen = 4*(HLen - ?IP_MIN_HDR_LEN),
    │ │ │ │ +        <<Opts:OptsLen/binary,Data/binary>> = RestDgram,
    │ │ │ │      ...
    │ │ │ │  end.

    Here, the segment corresponding to the Opts variable has a type modifier, │ │ │ │ specifying that Opts is to bind to a binary. All other variables have the │ │ │ │ default type equal to unsigned integer.

    An IP datagram header is of variable length. This length is measured in the │ │ │ │ number of 32-bit words and is given in the segment corresponding to HLen. The │ │ │ │ minimum value of HLen is 5. It is the segment corresponding to Opts that is │ │ │ │ variable, so if HLen is equal to 5, Opts becomes an empty binary.

    The tail variables RestDgram and Data bind to binaries, as all tail │ │ │ │ @@ -123,77 +123,77 @@ │ │ │ │

    This section describes the rules for constructing binaries using the bit syntax. │ │ │ │ Unlike when constructing lists or tuples, the construction of a binary can fail │ │ │ │ with a badarg exception.

    There can be zero or more segments in a binary to be constructed. The expression │ │ │ │ <<>> constructs a zero length binary.

    Each segment in a binary can consist of zero or more bits. There are no │ │ │ │ alignment rules for individual segments of type integer and float. For │ │ │ │ binaries and bitstrings without size, the unit specifies the alignment. Since │ │ │ │ the default alignment for the binary type is 8, the size of a binary segment │ │ │ │ -must be a multiple of 8 bits, that is, only whole bytes.

    Example:

    <<Bin/binary,Bitstring/bitstring>>

    The variable Bin must contain a whole number of bytes, because the binary │ │ │ │ +must be a multiple of 8 bits, that is, only whole bytes.

    Example:

    <<Bin/binary,Bitstring/bitstring>>

    The variable Bin must contain a whole number of bytes, because the binary │ │ │ │ type defaults to unit:8. A badarg exception is generated if Bin consist │ │ │ │ of, for example, 17 bits.

    The Bitstring variable can consist of any number of bits, for example, 0, 1, │ │ │ │ 8, 11, 17, 42, and so on. This is because the default unit for bitstrings │ │ │ │ is 1.

    For clarity, it is recommended not to change the unit size for binaries. │ │ │ │ Instead, use binary when you need byte alignment and bitstring when you need │ │ │ │ bit alignment.

    The following example successfully constructs a bitstring of 7 bits, provided │ │ │ │ -that all of X and Y are integers:

    <<X:1,Y:6>>

    As mentioned earlier, segments have the following general syntax:

    Value:Size/TypeSpecifierList

    When constructing binaries, Value and Size can be any Erlang expression. │ │ │ │ +that all of X and Y are integers:

    <<X:1,Y:6>>

    As mentioned earlier, segments have the following general syntax:

    Value:Size/TypeSpecifierList

    When constructing binaries, Value and Size can be any Erlang expression. │ │ │ │ However, for syntactical reasons, both Value and Size must be enclosed in │ │ │ │ parenthesis if the expression consists of anything more than a single literal or │ │ │ │ -a variable. The following gives a compiler syntax error:

    <<X+1:8>>

    This expression must be rewritten into the following, to be accepted by the │ │ │ │ -compiler:

    <<(X+1):8>>

    │ │ │ │ +a variable. The following gives a compiler syntax error:

    <<X+1:8>>

    This expression must be rewritten into the following, to be accepted by the │ │ │ │ +compiler:

    <<(X+1):8>>

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Including Literal Strings │ │ │ │

    │ │ │ │ -

    A literal string can be written instead of an element:

    <<"hello">>

    This is syntactic sugar for the following:

    <<$h,$e,$l,$l,$o>>

    │ │ │ │ +

    A literal string can be written instead of an element:

    <<"hello">>

    This is syntactic sugar for the following:

    <<$h,$e,$l,$l,$o>>

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Matching Binaries │ │ │ │

    │ │ │ │

    This section describes the rules for matching binaries, using the bit syntax.

    There can be zero or more segments in a binary pattern. A binary pattern can │ │ │ │ occur wherever patterns are allowed, including inside other patterns. Binary │ │ │ │ patterns cannot be nested. The pattern <<>> matches a zero length binary.

    Each segment in a binary can consist of zero or more bits. A segment of type │ │ │ │ binary must have a size evenly divisible by 8 (or divisible by the unit size, │ │ │ │ if the unit size has been changed). A segment of type bitstring has no │ │ │ │ restrictions on the size. A segment of type float must have size 64 or 32.

    As mentioned earlier, segments have the following general syntax:

    Value:Size/TypeSpecifierList

    When matching Value, value must be either a variable or an integer, or a │ │ │ │ floating point literal. Expressions are not allowed.

    Size must be a │ │ │ │ guard expression, which can use │ │ │ │ -literals and previously bound variables. The following is not allowed:

    foo(N, <<X:N,T/binary>>) ->
    │ │ │ │ -   {X,T}.

    The two occurrences of N are not related. The compiler will complain that the │ │ │ │ -N in the size field is unbound.

    The correct way to write this example is as follows:

    foo(N, Bin) ->
    │ │ │ │ -   <<X:N,T/binary>> = Bin,
    │ │ │ │ -   {X,T}.

    Note

    Before OTP 23, Size was restricted to be an integer or a variable bound to │ │ │ │ +literals and previously bound variables. The following is not allowed:

    foo(N, <<X:N,T/binary>>) ->
    │ │ │ │ +   {X,T}.

    The two occurrences of N are not related. The compiler will complain that the │ │ │ │ +N in the size field is unbound.

    The correct way to write this example is as follows:

    foo(N, Bin) ->
    │ │ │ │ +   <<X:N,T/binary>> = Bin,
    │ │ │ │ +   {X,T}.

    Note

    Before OTP 23, Size was restricted to be an integer or a variable bound to │ │ │ │ an integer.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Binding and Using a Size Variable │ │ │ │

    │ │ │ │

    There is one exception to the rule that a variable that is used as size must be │ │ │ │ previously bound. It is possible to match and bind a variable, and use it as a │ │ │ │ -size within the same binary pattern. For example:

    bar(<<Sz:8,Payload:Sz/binary-unit:8,Rest/binary>>) ->
    │ │ │ │ -   {Payload,Rest}.

    Here Sz is bound to the value in the first byte of the binary. Sz is then │ │ │ │ -used at the number of bytes to match out as a binary.

    Starting in OTP 23, the size can be a guard expression:

    bar(<<Sz:8,Payload:((Sz-1)*8)/binary,Rest/binary>>) ->
    │ │ │ │ -   {Payload,Rest}.

    Here Sz is the combined size of the header and the payload, so we will need to │ │ │ │ +size within the same binary pattern. For example:

    bar(<<Sz:8,Payload:Sz/binary-unit:8,Rest/binary>>) ->
    │ │ │ │ +   {Payload,Rest}.

    Here Sz is bound to the value in the first byte of the binary. Sz is then │ │ │ │ +used at the number of bytes to match out as a binary.

    Starting in OTP 23, the size can be a guard expression:

    bar(<<Sz:8,Payload:((Sz-1)*8)/binary,Rest/binary>>) ->
    │ │ │ │ +   {Payload,Rest}.

    Here Sz is the combined size of the header and the payload, so we will need to │ │ │ │ subtract one byte to get the size of the payload.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Getting the Rest of the Binary or Bitstring │ │ │ │

    │ │ │ │ -

    To match out the rest of a binary, specify a binary field without size:

    foo(<<A:8,Rest/binary>>) ->

    The size of the tail must be evenly divisible by 8.

    To match out the rest of a bitstring, specify a field without size:

    foo(<<A:8,Rest/bitstring>>) ->

    There are no restrictions on the number of bits in the tail.

    │ │ │ │ +

    To match out the rest of a binary, specify a binary field without size:

    foo(<<A:8,Rest/binary>>) ->

    The size of the tail must be evenly divisible by 8.

    To match out the rest of a bitstring, specify a field without size:

    foo(<<A:8,Rest/bitstring>>) ->

    There are no restrictions on the number of bits in the tail.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Appending to a Binary │ │ │ │

    │ │ │ │ -

    Appending to a binary in an efficient way can be done as follows:

    triples_to_bin(T) ->
    │ │ │ │ -    triples_to_bin(T, <<>>).
    │ │ │ │ +

    Appending to a binary in an efficient way can be done as follows:

    triples_to_bin(T) ->
    │ │ │ │ +    triples_to_bin(T, <<>>).
    │ │ │ │  
    │ │ │ │ -triples_to_bin([{X,Y,Z} | T], Acc) ->
    │ │ │ │ -    triples_to_bin(T, <<Acc/binary,X:32,Y:32,Z:32>>);
    │ │ │ │ -triples_to_bin([], Acc) ->
    │ │ │ │ +triples_to_bin([{X,Y,Z} | T], Acc) ->
    │ │ │ │ +    triples_to_bin(T, <<Acc/binary,X:32,Y:32,Z:32>>);
    │ │ │ │ +triples_to_bin([], Acc) ->
    │ │ │ │      Acc.
    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/binaryhandling.xhtml │ │ │ │ @@ -19,43 +19,43 @@ │ │ │ │ │ │ │ │

    │ │ │ │ Constructing and Matching Binaries │ │ │ │

    │ │ │ │

    This section gives a few examples on how to handle binaries in an efficient way. │ │ │ │ The sections that follow take an in-depth look at how binaries are implemented │ │ │ │ and how to best take advantages of the optimizations done by the compiler and │ │ │ │ -runtime system.

    Binaries can be efficiently built in the following way:

    DO

    my_list_to_binary(List) ->
    │ │ │ │ -    my_list_to_binary(List, <<>>).
    │ │ │ │ +runtime system.

    Binaries can be efficiently built in the following way:

    DO

    my_list_to_binary(List) ->
    │ │ │ │ +    my_list_to_binary(List, <<>>).
    │ │ │ │  
    │ │ │ │ -my_list_to_binary([H|T], Acc) ->
    │ │ │ │ -    my_list_to_binary(T, <<Acc/binary,H>>);
    │ │ │ │ -my_list_to_binary([], Acc) ->
    │ │ │ │ +my_list_to_binary([H|T], Acc) ->
    │ │ │ │ +    my_list_to_binary(T, <<Acc/binary,H>>);
    │ │ │ │ +my_list_to_binary([], Acc) ->
    │ │ │ │      Acc.

    Appending data to a binary as in the example is efficient because it is │ │ │ │ specially optimized by the runtime system to avoid copying the Acc binary │ │ │ │ -every time.

    Prepending data to a binary in a loop is not efficient:

    DO NOT

    rev_list_to_binary(List) ->
    │ │ │ │ -    rev_list_to_binary(List, <<>>).
    │ │ │ │ +every time.

    Prepending data to a binary in a loop is not efficient:

    DO NOT

    rev_list_to_binary(List) ->
    │ │ │ │ +    rev_list_to_binary(List, <<>>).
    │ │ │ │  
    │ │ │ │ -rev_list_to_binary([H|T], Acc) ->
    │ │ │ │ -    rev_list_to_binary(T, <<H,Acc/binary>>);
    │ │ │ │ -rev_list_to_binary([], Acc) ->
    │ │ │ │ +rev_list_to_binary([H|T], Acc) ->
    │ │ │ │ +    rev_list_to_binary(T, <<H,Acc/binary>>);
    │ │ │ │ +rev_list_to_binary([], Acc) ->
    │ │ │ │      Acc.

    This is not efficient for long lists because the Acc binary is copied every │ │ │ │ -time. One way to make the function more efficient is like this:

    DO NOT

    rev_list_to_binary(List) ->
    │ │ │ │ -    rev_list_to_binary(lists:reverse(List), <<>>).
    │ │ │ │ +time. One way to make the function more efficient is like this:

    DO NOT

    rev_list_to_binary(List) ->
    │ │ │ │ +    rev_list_to_binary(lists:reverse(List), <<>>).
    │ │ │ │  
    │ │ │ │ -rev_list_to_binary([H|T], Acc) ->
    │ │ │ │ -    rev_list_to_binary(T, <<Acc/binary,H>>);
    │ │ │ │ -rev_list_to_binary([], Acc) ->
    │ │ │ │ -    Acc.

    Another way to avoid copying the binary each time is like this:

    DO

    rev_list_to_binary([H|T]) ->
    │ │ │ │ -    RevTail = rev_list_to_binary(T),
    │ │ │ │ -    <<RevTail/binary,H>>;
    │ │ │ │ -rev_list_to_binary([]) ->
    │ │ │ │ -    <<>>.

    Note that in each of the DO examples, the binary to be appended to is always │ │ │ │ -given as the first segment.

    Binaries can be efficiently matched in the following way:

    DO

    my_binary_to_list(<<H,T/binary>>) ->
    │ │ │ │ -    [H|my_binary_to_list(T)];
    │ │ │ │ -my_binary_to_list(<<>>) -> [].

    │ │ │ │ +rev_list_to_binary([H|T], Acc) -> │ │ │ │ + rev_list_to_binary(T, <<Acc/binary,H>>); │ │ │ │ +rev_list_to_binary([], Acc) -> │ │ │ │ + Acc.

    Another way to avoid copying the binary each time is like this:

    DO

    rev_list_to_binary([H|T]) ->
    │ │ │ │ +    RevTail = rev_list_to_binary(T),
    │ │ │ │ +    <<RevTail/binary,H>>;
    │ │ │ │ +rev_list_to_binary([]) ->
    │ │ │ │ +    <<>>.

    Note that in each of the DO examples, the binary to be appended to is always │ │ │ │ +given as the first segment.

    Binaries can be efficiently matched in the following way:

    DO

    my_binary_to_list(<<H,T/binary>>) ->
    │ │ │ │ +    [H|my_binary_to_list(T)];
    │ │ │ │ +my_binary_to_list(<<>>) -> [].

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ How Binaries are Implemented │ │ │ │

    │ │ │ │

    Internally, binaries and bitstrings are implemented in the same way. In this │ │ │ │ section, they are called binaries because that is what they are called in the │ │ │ │ @@ -110,29 +110,29 @@ │ │ │ │ called referential transparency) of Erlang would break.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Constructing Binaries │ │ │ │

    │ │ │ │

    Appending to a binary or bitstring in the following way is specially optimized │ │ │ │ -to avoid copying the binary:

    <<Binary/binary, ...>>
    │ │ │ │ +to avoid copying the binary:

    <<Binary/binary, ...>>
    │ │ │ │  %% - OR -
    │ │ │ │ -<<Binary/bitstring, ...>>

    This optimization is applied by the runtime system in a way that makes it │ │ │ │ +<<Binary/bitstring, ...>>

    This optimization is applied by the runtime system in a way that makes it │ │ │ │ effective in most circumstances (for exceptions, see │ │ │ │ Circumstances That Force Copying). The │ │ │ │ optimization in its basic form does not need any help from the compiler. │ │ │ │ However, the compiler add hints to the runtime system when it is safe to apply │ │ │ │ the optimization in a more efficient way.

    Change

    The compiler support for making the optimization more efficient was added in │ │ │ │ Erlang/OTP 26.

    To explain how the basic optimization works, let us examine the following code │ │ │ │ -line by line:

    Bin0 = <<0>>,                    %% 1
    │ │ │ │ -Bin1 = <<Bin0/binary,1,2,3>>,    %% 2
    │ │ │ │ -Bin2 = <<Bin1/binary,4,5,6>>,    %% 3
    │ │ │ │ -Bin3 = <<Bin2/binary,7,8,9>>,    %% 4
    │ │ │ │ -Bin4 = <<Bin1/binary,17>>,       %% 5 !!!
    │ │ │ │ -{Bin4,Bin3}                      %% 6
    • Line 1 (marked with the %% 1 comment), assigns a │ │ │ │ +line by line:

      Bin0 = <<0>>,                    %% 1
      │ │ │ │ +Bin1 = <<Bin0/binary,1,2,3>>,    %% 2
      │ │ │ │ +Bin2 = <<Bin1/binary,4,5,6>>,    %% 3
      │ │ │ │ +Bin3 = <<Bin2/binary,7,8,9>>,    %% 4
      │ │ │ │ +Bin4 = <<Bin1/binary,17>>,       %% 5 !!!
      │ │ │ │ +{Bin4,Bin3}                      %% 6
      • Line 1 (marked with the %% 1 comment), assigns a │ │ │ │ heap binary to the Bin0 variable.

      • Line 2 is an append operation. As Bin0 has not been involved in an append │ │ │ │ operation, a new refc binary is created and │ │ │ │ the contents of Bin0 is copied into it. The ProcBin part of the refc │ │ │ │ binary has its size set to the size of the data stored in the binary, while │ │ │ │ the binary object has extra space allocated. The size of the binary object is │ │ │ │ either twice the size of Bin1 or 256, whichever is larger. In this case it │ │ │ │ is 256.

      • Line 3 is more interesting. Bin1 has been used in an append operation, and │ │ │ │ @@ -158,23 +158,23 @@ │ │ │ │ handle an append operation to a heap binary by copying it to a refc binary (line │ │ │ │ 2), and also handle an append operation to a previous version of the binary by │ │ │ │ copying it (line 5). The support for doing that does not come for free. For │ │ │ │ example, to make it possible to know when it is necessary to copy the binary, │ │ │ │ for every append operation, the runtime system must create a sub binary.

        When the compiler can determine that none of those situations need to be handled │ │ │ │ and that the append operation cannot possibly fail, the compiler generates code │ │ │ │ that causes the runtime system to apply a more efficient variant of the │ │ │ │ -optimization.

        Example:

        -module(repack).
        │ │ │ │ --export([repack/1]).
        │ │ │ │ +optimization.

        Example:

        -module(repack).
        │ │ │ │ +-export([repack/1]).
        │ │ │ │  
        │ │ │ │ -repack(Bin) when is_binary(Bin) ->
        │ │ │ │ -    repack(Bin, <<>>).
        │ │ │ │ +repack(Bin) when is_binary(Bin) ->
        │ │ │ │ +    repack(Bin, <<>>).
        │ │ │ │  
        │ │ │ │ -repack(<<C:8,T/binary>>, Result) ->
        │ │ │ │ -    repack(T, <<Result/binary,C:16>>);
        │ │ │ │ -repack(<<>>, Result) ->
        │ │ │ │ +repack(<<C:8,T/binary>>, Result) ->
        │ │ │ │ +    repack(T, <<Result/binary,C:16>>);
        │ │ │ │ +repack(<<>>, Result) ->
        │ │ │ │      Result.

        The repack/2 function only keeps a single version of the binary, so there is │ │ │ │ never any need to copy the binary. The compiler rewrites the creation of the │ │ │ │ empty binary in repack/1 to instead create a refc binary with 256 bytes │ │ │ │ already reserved; thus, the append operation in repack/2 never needs to handle │ │ │ │ a binary not prepared for appending.

        │ │ │ │ │ │ │ │ │ │ │ │ @@ -186,72 +186,72 @@ │ │ │ │ reason is that the binary object can be moved (reallocated) during an append │ │ │ │ operation, and when that happens, the pointer in the ProcBin must be updated. If │ │ │ │ there would be more than one ProcBin pointing to the binary object, it would not │ │ │ │ be possible to find and update all of them.

        Therefore, certain operations on a binary mark it so that any future append │ │ │ │ operation will be forced to copy the binary. In most cases, the binary object │ │ │ │ will be shrunk at the same time to reclaim the extra space allocated for │ │ │ │ growing.

        When appending to a binary as follows, only the binary returned from the latest │ │ │ │ -append operation will support further cheap append operations:

        Bin = <<Bin0,...>>

        In the code fragment in the beginning of this section, appending to Bin will │ │ │ │ +append operation will support further cheap append operations:

        Bin = <<Bin0,...>>

        In the code fragment in the beginning of this section, appending to Bin will │ │ │ │ be cheap, while appending to Bin0 will force the creation of a new binary and │ │ │ │ copying of the contents of Bin0.

        If a binary is sent as a message to a process or port, the binary will be shrunk │ │ │ │ and any further append operation will copy the binary data into a new binary. │ │ │ │ For example, in the following code fragment Bin1 will be copied in the third │ │ │ │ -line:

        Bin1 = <<Bin0,...>>,
        │ │ │ │ +line:

        Bin1 = <<Bin0,...>>,
        │ │ │ │  PortOrPid ! Bin1,
        │ │ │ │ -Bin = <<Bin1,...>>  %% Bin1 will be COPIED

        The same happens if you insert a binary into an Ets table, send it to a port │ │ │ │ +Bin = <<Bin1,...>> %% Bin1 will be COPIED

        The same happens if you insert a binary into an Ets table, send it to a port │ │ │ │ using erlang:port_command/2, or pass it to │ │ │ │ enif_inspect_binary in a NIF.

        Matching a binary will also cause it to shrink and the next append operation │ │ │ │ -will copy the binary data:

        Bin1 = <<Bin0,...>>,
        │ │ │ │ -<<X,Y,Z,T/binary>> = Bin1,
        │ │ │ │ -Bin = <<Bin1,...>>  %% Bin1 will be COPIED

        The reason is that a match context contains a │ │ │ │ +will copy the binary data:

        Bin1 = <<Bin0,...>>,
        │ │ │ │ +<<X,Y,Z,T/binary>> = Bin1,
        │ │ │ │ +Bin = <<Bin1,...>>  %% Bin1 will be COPIED

        The reason is that a match context contains a │ │ │ │ direct pointer to the binary data.

        If a process simply keeps binaries (either in "loop data" or in the process │ │ │ │ dictionary), the garbage collector can eventually shrink the binaries. If only │ │ │ │ one such binary is kept, it will not be shrunk. If the process later appends to │ │ │ │ a binary that has been shrunk, the binary object will be reallocated to make │ │ │ │ place for the data to be appended.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Matching Binaries │ │ │ │

        │ │ │ │ -

        Let us revisit the example in the beginning of the previous section:

        DO

        my_binary_to_list(<<H,T/binary>>) ->
        │ │ │ │ -    [H|my_binary_to_list(T)];
        │ │ │ │ -my_binary_to_list(<<>>) -> [].

        The first time my_binary_to_list/1 is called, a │ │ │ │ +

        Let us revisit the example in the beginning of the previous section:

        DO

        my_binary_to_list(<<H,T/binary>>) ->
        │ │ │ │ +    [H|my_binary_to_list(T)];
        │ │ │ │ +my_binary_to_list(<<>>) -> [].

        The first time my_binary_to_list/1 is called, a │ │ │ │ match context is created. The match context │ │ │ │ points to the first byte of the binary. 1 byte is matched out and the match │ │ │ │ context is updated to point to the second byte in the binary.

        At this point it would make sense to create a │ │ │ │ sub binary, but in this particular example the │ │ │ │ compiler sees that there will soon be a call to a function (in this case, to │ │ │ │ my_binary_to_list/1 itself) that immediately will create a new match context │ │ │ │ and discard the sub binary.

        Therefore my_binary_to_list/1 calls itself with the match context instead of │ │ │ │ with a sub binary. The instruction that initializes the matching operation │ │ │ │ basically does nothing when it sees that it was passed a match context instead │ │ │ │ of a binary.

        When the end of the binary is reached and the second clause matches, the match │ │ │ │ context will simply be discarded (removed in the next garbage collection, as │ │ │ │ there is no longer any reference to it).

        To summarize, my_binary_to_list/1 only needs to create one match context and │ │ │ │ no sub binaries.

        Notice that the match context in my_binary_to_list/1 was discarded when the │ │ │ │ entire binary had been traversed. What happens if the iteration stops before it │ │ │ │ -has reached the end of the binary? Will the optimization still work?

        after_zero(<<0,T/binary>>) ->
        │ │ │ │ +has reached the end of the binary? Will the optimization still work?

        after_zero(<<0,T/binary>>) ->
        │ │ │ │      T;
        │ │ │ │ -after_zero(<<_,T/binary>>) ->
        │ │ │ │ -    after_zero(T);
        │ │ │ │ -after_zero(<<>>) ->
        │ │ │ │ -    <<>>.

        Yes, it will. The compiler will remove the building of the sub binary in the │ │ │ │ +after_zero(<<_,T/binary>>) -> │ │ │ │ + after_zero(T); │ │ │ │ +after_zero(<<>>) -> │ │ │ │ + <<>>.

        Yes, it will. The compiler will remove the building of the sub binary in the │ │ │ │ second clause:

        ...
        │ │ │ │ -after_zero(<<_,T/binary>>) ->
        │ │ │ │ -    after_zero(T);
        │ │ │ │ -...

        But it will generate code that builds a sub binary in the first clause:

        after_zero(<<0,T/binary>>) ->
        │ │ │ │ +after_zero(<<_,T/binary>>) ->
        │ │ │ │ +    after_zero(T);
        │ │ │ │ +...

        But it will generate code that builds a sub binary in the first clause:

        after_zero(<<0,T/binary>>) ->
        │ │ │ │      T;
        │ │ │ │  ...

        Therefore, after_zero/1 builds one match context and one sub binary (assuming │ │ │ │ -it is passed a binary that contains a zero byte).

        Code like the following will also be optimized:

        all_but_zeroes_to_list(Buffer, Acc, 0) ->
        │ │ │ │ -    {lists:reverse(Acc),Buffer};
        │ │ │ │ -all_but_zeroes_to_list(<<0,T/binary>>, Acc, Remaining) ->
        │ │ │ │ -    all_but_zeroes_to_list(T, Acc, Remaining-1);
        │ │ │ │ -all_but_zeroes_to_list(<<Byte,T/binary>>, Acc, Remaining) ->
        │ │ │ │ -    all_but_zeroes_to_list(T, [Byte|Acc], Remaining-1).

        The compiler removes building of sub binaries in the second and third clauses, │ │ │ │ +it is passed a binary that contains a zero byte).

        Code like the following will also be optimized:

        all_but_zeroes_to_list(Buffer, Acc, 0) ->
        │ │ │ │ +    {lists:reverse(Acc),Buffer};
        │ │ │ │ +all_but_zeroes_to_list(<<0,T/binary>>, Acc, Remaining) ->
        │ │ │ │ +    all_but_zeroes_to_list(T, Acc, Remaining-1);
        │ │ │ │ +all_but_zeroes_to_list(<<Byte,T/binary>>, Acc, Remaining) ->
        │ │ │ │ +    all_but_zeroes_to_list(T, [Byte|Acc], Remaining-1).

        The compiler removes building of sub binaries in the second and third clauses, │ │ │ │ and it adds an instruction to the first clause that converts Buffer from a │ │ │ │ match context to a sub binary (or do nothing if Buffer is a binary already).

        But in more complicated code, how can one know whether the optimization is │ │ │ │ applied or not?

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Option bin_opt_info │ │ │ │ @@ -259,35 +259,35 @@ │ │ │ │

        Use the bin_opt_info option to have the compiler print a lot of information │ │ │ │ about binary optimizations. It can be given either to the compiler or erlc:

        erlc +bin_opt_info Mod.erl

        or passed through an environment variable:

        export ERL_COMPILER_OPTIONS=bin_opt_info

        Notice that the bin_opt_info is not meant to be a permanent option added to │ │ │ │ your Makefiles, because all messages that it generates cannot be eliminated. │ │ │ │ Therefore, passing the option through the environment is in most cases the most │ │ │ │ practical approach.

        The warnings look as follows:

        ./efficiency_guide.erl:60: Warning: NOT OPTIMIZED: binary is returned from the function
        │ │ │ │  ./efficiency_guide.erl:62: Warning: OPTIMIZED: match context reused

        To make it clearer exactly what code the warnings refer to, the warnings in the │ │ │ │ following examples are inserted as comments after the clause they refer to, for │ │ │ │ -example:

        after_zero(<<0,T/binary>>) ->
        │ │ │ │ +example:

        after_zero(<<0,T/binary>>) ->
        │ │ │ │           %% BINARY CREATED: binary is returned from the function
        │ │ │ │      T;
        │ │ │ │ -after_zero(<<_,T/binary>>) ->
        │ │ │ │ +after_zero(<<_,T/binary>>) ->
        │ │ │ │           %% OPTIMIZED: match context reused
        │ │ │ │ -    after_zero(T);
        │ │ │ │ -after_zero(<<>>) ->
        │ │ │ │ -    <<>>.

        The warning for the first clause says that the creation of a sub binary cannot │ │ │ │ + after_zero(T); │ │ │ │ +after_zero(<<>>) -> │ │ │ │ + <<>>.

        The warning for the first clause says that the creation of a sub binary cannot │ │ │ │ be delayed, because it will be returned. The warning for the second clause says │ │ │ │ that a sub binary will not be created (yet).

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Unused Variables │ │ │ │

        │ │ │ │

        The compiler figures out if a variable is unused. The same code is generated for │ │ │ │ -each of the following functions:

        count1(<<_,T/binary>>, Count) -> count1(T, Count+1);
        │ │ │ │ -count1(<<>>, Count) -> Count.
        │ │ │ │ +each of the following functions:

        count1(<<_,T/binary>>, Count) -> count1(T, Count+1);
        │ │ │ │ +count1(<<>>, Count) -> Count.
        │ │ │ │  
        │ │ │ │ -count2(<<H,T/binary>>, Count) -> count2(T, Count+1);
        │ │ │ │ -count2(<<>>, Count) -> Count.
        │ │ │ │ +count2(<<H,T/binary>>, Count) -> count2(T, Count+1);
        │ │ │ │ +count2(<<>>, Count) -> Count.
        │ │ │ │  
        │ │ │ │ -count3(<<_H,T/binary>>, Count) -> count3(T, Count+1);
        │ │ │ │ -count3(<<>>, Count) -> Count.

        In each iteration, the first 8 bits in the binary will be skipped, not matched │ │ │ │ +count3(<<_H,T/binary>>, Count) -> count3(T, Count+1); │ │ │ │ +count3(<<>>, Count) -> Count.

        In each iteration, the first 8 bits in the binary will be skipped, not matched │ │ │ │ out.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/benchmarking.xhtml │ │ │ │ @@ -49,16 +49,16 @@ │ │ │ │ fast as possible, what can we do? One way could be to generate more │ │ │ │ than two bytes at the time.

        % erlperf 'rand:bytes(100).' 'crypto:strong_rand_bytes(100).'
        │ │ │ │  Code                                   ||        QPS       Time   Rel
        │ │ │ │  rand:bytes(100).                        1    2124 Ki     470 ns  100%
        │ │ │ │  crypto:strong_rand_bytes(100).          1    1915 Ki     522 ns   90%

        rand:bytes/1 is still faster when we generate 100 bytes at the time, │ │ │ │ but the relative difference is smaller.

        % erlperf 'rand:bytes(1000).' 'crypto:strong_rand_bytes(1000).'
        │ │ │ │  Code                                    ||        QPS       Time   Rel
        │ │ │ │ -crypto:strong_rand_bytes(1000).          1    1518 Ki     658 ns  100%
        │ │ │ │ -rand:bytes(1000).                        1     284 Ki    3521 ns   19%

        When we generate 1000 bytes at the time, crypto:strong_rand_bytes/1 is │ │ │ │ +crypto:strong_rand_bytes(1000). 1 1518 Ki 658 ns 100% │ │ │ │ +rand:bytes(1000). 1 284 Ki 3521 ns 19%

        When we generate 1000 bytes at the time, crypto:strong_rand_bytes/1 is │ │ │ │ now the fastest.

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Benchmarking using Erlang/OTP functionality │ │ │ │

        │ │ │ │

        Benchmarks can measure wall-clock time or CPU time.

        • timer:tc/3 measures wall-clock time. The advantage with wall-clock time is │ │ │ ├── OEBPS/appup_cookbook.xhtml │ │ │ │ @@ -25,18 +25,18 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Changing a Functional Module │ │ │ │ │ │ │ │

          When a functional module has been changed, for example, if a new function has │ │ │ │ been added or a bug has been corrected, simple code replacement is sufficient, │ │ │ │ -for example:

          {"2",
          │ │ │ │ - [{"1", [{load_module, m}]}],
          │ │ │ │ - [{"1", [{load_module, m}]}]
          │ │ │ │ -}.

          │ │ │ │ +for example:

          {"2",
          │ │ │ │ + [{"1", [{load_module, m}]}],
          │ │ │ │ + [{"1", [{load_module, m}]}]
          │ │ │ │ +}.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Changing a Residence Module │ │ │ │

          │ │ │ │

          In a system implemented according to the OTP design principles, all processes, │ │ │ │ except system processes and special processes, reside in one of the behaviours │ │ │ │ @@ -47,46 +47,46 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Changing a Callback Module │ │ │ │ │ │ │ │

          A callback module is a functional module, and for code extensions simple code │ │ │ │ replacement is sufficient.

          Example

          When adding a function to ch3, as described in the example in │ │ │ │ -Release Handling, ch_app.appup looks as follows:

          {"2",
          │ │ │ │ - [{"1", [{load_module, ch3}]}],
          │ │ │ │ - [{"1", [{load_module, ch3}]}]
          │ │ │ │ -}.

          OTP also supports changing the internal state of behaviour processes; see │ │ │ │ +Release Handling, ch_app.appup looks as follows:

          {"2",
          │ │ │ │ + [{"1", [{load_module, ch3}]}],
          │ │ │ │ + [{"1", [{load_module, ch3}]}]
          │ │ │ │ +}.

          OTP also supports changing the internal state of behaviour processes; see │ │ │ │ Changing Internal State.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Changing Internal State │ │ │ │

          │ │ │ │

          In this case, simple code replacement is not sufficient. The process must │ │ │ │ explicitly transform its state using the callback function code_change/3 before │ │ │ │ switching to the new version of the callback module. Thus, synchronized code │ │ │ │ replacement is used.

          Example

          Consider the ch3 module from │ │ │ │ gen_server Behaviour. The internal state is a term │ │ │ │ Chs representing the available channels. Assume you want to add a counter N, │ │ │ │ which keeps track of the number of alloc requests so far. This means that the │ │ │ │ -format must be changed to {Chs,N}.

          The .appup file can look as follows:

          {"2",
          │ │ │ │ - [{"1", [{update, ch3, {advanced, []}}]}],
          │ │ │ │ - [{"1", [{update, ch3, {advanced, []}}]}]
          │ │ │ │ -}.

          The third element of the update instruction is a tuple {advanced,Extra}, │ │ │ │ +format must be changed to {Chs,N}.

          The .appup file can look as follows:

          {"2",
          │ │ │ │ + [{"1", [{update, ch3, {advanced, []}}]}],
          │ │ │ │ + [{"1", [{update, ch3, {advanced, []}}]}]
          │ │ │ │ +}.

          The third element of the update instruction is a tuple {advanced,Extra}, │ │ │ │ which says that the affected processes are to do a state transformation before │ │ │ │ loading the new version of the module. This is done by the processes calling the │ │ │ │ callback function code_change/3 (see gen_server in STDLIB). │ │ │ │ -The term Extra, in this case [], is passed as is to the function:

          -module(ch3).
          │ │ │ │ +The term Extra, in this case [], is passed as is to the function:

          -module(ch3).
          │ │ │ │  ...
          │ │ │ │ --export([code_change/3]).
          │ │ │ │ +-export([code_change/3]).
          │ │ │ │  ...
          │ │ │ │ -code_change({down, _Vsn}, {Chs, N}, _Extra) ->
          │ │ │ │ -    {ok, Chs};
          │ │ │ │ -code_change(_Vsn, Chs, _Extra) ->
          │ │ │ │ -    {ok, {Chs, 0}}.

          The first argument is {down,Vsn} if there is a downgrade, or Vsn if there is │ │ │ │ +code_change({down, _Vsn}, {Chs, N}, _Extra) -> │ │ │ │ + {ok, Chs}; │ │ │ │ +code_change(_Vsn, Chs, _Extra) -> │ │ │ │ + {ok, {Chs, 0}}.

          The first argument is {down,Vsn} if there is a downgrade, or Vsn if there is │ │ │ │ a upgrade. The term Vsn is fetched from the 'original' version of the module, │ │ │ │ that is, the version you are upgrading from, or downgrading to.

          The version is defined by the module attribute vsn, if any. There is no such │ │ │ │ attribute in ch3, so in this case the version is the checksum (a huge integer) │ │ │ │ of the beam file, an uninteresting value, which is ignored.

          The other callback functions of ch3 must also be modified and perhaps a new │ │ │ │ interface function must be added, but this is not shown here.

          │ │ │ │ │ │ │ │ │ │ │ │ @@ -95,67 +95,67 @@ │ │ │ │

          │ │ │ │

          Assume that a module is extended by adding an interface function, as in the │ │ │ │ example in Release Handling, where a function │ │ │ │ available/0 is added to ch3.

          If a call is added to this function, say in module m1, a runtime error could │ │ │ │ can occur during release upgrade if the new version of m1 is loaded first and │ │ │ │ calls ch3:available/0 before the new version of ch3 is loaded.

          Thus, ch3 must be loaded before m1, in the upgrade case, and conversely in │ │ │ │ the downgrade case. m1 is said to be dependent on ch3. In a release │ │ │ │ -handling instruction, this is expressed by the DepMods element:

          {load_module, Module, DepMods}
          │ │ │ │ -{update, Module, {advanced, Extra}, DepMods}

          DepMods is a list of modules, on which Module is dependent.

          Example

          The module m1 in application myapp is dependent on ch3 when │ │ │ │ +handling instruction, this is expressed by the DepMods element:

          {load_module, Module, DepMods}
          │ │ │ │ +{update, Module, {advanced, Extra}, DepMods}

          DepMods is a list of modules, on which Module is dependent.

          Example

          The module m1 in application myapp is dependent on ch3 when │ │ │ │ upgrading from "1" to "2", or downgrading from "2" to "1":

          myapp.appup:
          │ │ │ │  
          │ │ │ │ -{"2",
          │ │ │ │ - [{"1", [{load_module, m1, [ch3]}]}],
          │ │ │ │ - [{"1", [{load_module, m1, [ch3]}]}]
          │ │ │ │ -}.
          │ │ │ │ +{"2",
          │ │ │ │ + [{"1", [{load_module, m1, [ch3]}]}],
          │ │ │ │ + [{"1", [{load_module, m1, [ch3]}]}]
          │ │ │ │ +}.
          │ │ │ │  
          │ │ │ │  ch_app.appup:
          │ │ │ │  
          │ │ │ │ -{"2",
          │ │ │ │ - [{"1", [{load_module, ch3}]}],
          │ │ │ │ - [{"1", [{load_module, ch3}]}]
          │ │ │ │ -}.

          If instead m1 and ch3 belong to the same application, the .appup file can │ │ │ │ -look as follows:

          {"2",
          │ │ │ │ - [{"1",
          │ │ │ │ -   [{load_module, ch3},
          │ │ │ │ -    {load_module, m1, [ch3]}]}],
          │ │ │ │ - [{"1",
          │ │ │ │ -   [{load_module, ch3},
          │ │ │ │ -    {load_module, m1, [ch3]}]}]
          │ │ │ │ -}.

          m1 is dependent on ch3 also when downgrading. systools knows the │ │ │ │ +{"2", │ │ │ │ + [{"1", [{load_module, ch3}]}], │ │ │ │ + [{"1", [{load_module, ch3}]}] │ │ │ │ +}.

    If instead m1 and ch3 belong to the same application, the .appup file can │ │ │ │ +look as follows:

    {"2",
    │ │ │ │ + [{"1",
    │ │ │ │ +   [{load_module, ch3},
    │ │ │ │ +    {load_module, m1, [ch3]}]}],
    │ │ │ │ + [{"1",
    │ │ │ │ +   [{load_module, ch3},
    │ │ │ │ +    {load_module, m1, [ch3]}]}]
    │ │ │ │ +}.

    m1 is dependent on ch3 also when downgrading. systools knows the │ │ │ │ difference between up- and downgrading and generates a correct relup, where │ │ │ │ ch3 is loaded before m1 when upgrading, but m1 is loaded before ch3 when │ │ │ │ downgrading.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Changing Code for a Special Process │ │ │ │

    │ │ │ │

    In this case, simple code replacement is not sufficient. When a new version of a │ │ │ │ residence module for a special process is loaded, the process must make a fully │ │ │ │ qualified call to its loop function to switch to the new code. Thus, │ │ │ │ synchronized code replacement must be used.

    Note

    The name(s) of the user-defined residence module(s) must be listed in the │ │ │ │ Modules part of the child specification for the special process. Otherwise │ │ │ │ the release handler cannot find the process.

    Example

    Consider the example ch4 in sys and proc_lib. │ │ │ │ -When started by a supervisor, the child specification can look as follows:

    {ch4, {ch4, start_link, []},
    │ │ │ │ - permanent, brutal_kill, worker, [ch4]}

    If ch4 is part of the application sp_app and a new version of the module is │ │ │ │ +When started by a supervisor, the child specification can look as follows:

    {ch4, {ch4, start_link, []},
    │ │ │ │ + permanent, brutal_kill, worker, [ch4]}

    If ch4 is part of the application sp_app and a new version of the module is │ │ │ │ to be loaded when upgrading from version "1" to "2" of this application, │ │ │ │ -sp_app.appup can look as follows:

    {"2",
    │ │ │ │ - [{"1", [{update, ch4, {advanced, []}}]}],
    │ │ │ │ - [{"1", [{update, ch4, {advanced, []}}]}]
    │ │ │ │ -}.

    The update instruction must contain the tuple {advanced,Extra}. The │ │ │ │ +sp_app.appup can look as follows:

    {"2",
    │ │ │ │ + [{"1", [{update, ch4, {advanced, []}}]}],
    │ │ │ │ + [{"1", [{update, ch4, {advanced, []}}]}]
    │ │ │ │ +}.

    The update instruction must contain the tuple {advanced,Extra}. The │ │ │ │ instruction makes the special process call the callback function │ │ │ │ system_code_change/4, a function the user must implement. The term Extra, in │ │ │ │ -this case [], is passed as is to system_code_change/4:

    -module(ch4).
    │ │ │ │ +this case [], is passed as is to system_code_change/4:

    -module(ch4).
    │ │ │ │  ...
    │ │ │ │ --export([system_code_change/4]).
    │ │ │ │ +-export([system_code_change/4]).
    │ │ │ │  ...
    │ │ │ │  
    │ │ │ │ -system_code_change(Chs, _Module, _OldVsn, _Extra) ->
    │ │ │ │ -    {ok, Chs}.
    • The first argument is the internal state State, passed from │ │ │ │ +system_code_change(Chs, _Module, _OldVsn, _Extra) -> │ │ │ │ + {ok, Chs}.

    In this case, all arguments but the first are ignored and the function simply │ │ │ │ returns the internal state again. This is enough if the code only has been │ │ │ │ extended. If instead the internal state is changed (similar to the example in │ │ │ │ @@ -176,85 +176,85 @@ │ │ │ │ Changing Properties │ │ │ │ │ │ │ │

    Since the supervisor is to change its internal state, synchronized code │ │ │ │ replacement is required. However, a special update instruction must be used.

    First, the new version of the callback module must be loaded, both in the case │ │ │ │ of upgrade and downgrade. Then the new return value of init/1 can be checked │ │ │ │ and the internal state be changed accordingly.

    The following upgrade instruction is used for supervisors:

    {update, Module, supervisor}

    Example

    To change the restart strategy of ch_sup (from │ │ │ │ Supervisor Behaviour) from one_for_one to one_for_all, │ │ │ │ -change the callback function init/1 in ch_sup.erl:

    -module(ch_sup).
    │ │ │ │ +change the callback function init/1 in ch_sup.erl:

    -module(ch_sup).
    │ │ │ │  ...
    │ │ │ │  
    │ │ │ │ -init(_Args) ->
    │ │ │ │ -    {ok, {#{strategy => one_for_all, ...}, ...}}.

    The file ch_app.appup:

    {"2",
    │ │ │ │ - [{"1", [{update, ch_sup, supervisor}]}],
    │ │ │ │ - [{"1", [{update, ch_sup, supervisor}]}]
    │ │ │ │ -}.

    │ │ │ │ +init(_Args) -> │ │ │ │ + {ok, {#{strategy => one_for_all, ...}, ...}}.

    The file ch_app.appup:

    {"2",
    │ │ │ │ + [{"1", [{update, ch_sup, supervisor}]}],
    │ │ │ │ + [{"1", [{update, ch_sup, supervisor}]}]
    │ │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Changing Child Specifications │ │ │ │

    │ │ │ │

    The instruction, and thus the .appup file, when changing an existing child │ │ │ │ -specification, is the same as when changing properties as described earlier:

    {"2",
    │ │ │ │ - [{"1", [{update, ch_sup, supervisor}]}],
    │ │ │ │ - [{"1", [{update, ch_sup, supervisor}]}]
    │ │ │ │ -}.

    The changes do not affect existing child processes. For example, changing the │ │ │ │ +specification, is the same as when changing properties as described earlier:

    {"2",
    │ │ │ │ + [{"1", [{update, ch_sup, supervisor}]}],
    │ │ │ │ + [{"1", [{update, ch_sup, supervisor}]}]
    │ │ │ │ +}.

    The changes do not affect existing child processes. For example, changing the │ │ │ │ start function only specifies how the child process is to be restarted, if │ │ │ │ needed later on.

    The id of the child specification cannot be changed.

    Changing the Modules field of the child specification can affect the release │ │ │ │ handling process itself, as this field is used to identify which processes are │ │ │ │ affected when doing a synchronized code replacement.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Adding and Deleting Child Processes │ │ │ │

    │ │ │ │

    As stated earlier, changing child specifications does not affect existing child │ │ │ │ processes. New child specifications are automatically added, but not deleted. │ │ │ │ Child processes are not automatically started or terminated, this must be done │ │ │ │ using apply instructions.

    Example

    Assume a new child process m1 is to be added to ch_sup when │ │ │ │ upgrading ch_app from "1" to "2". This means m1 is to be deleted when │ │ │ │ -downgrading from "2" to "1":

    {"2",
    │ │ │ │ - [{"1",
    │ │ │ │ -   [{update, ch_sup, supervisor},
    │ │ │ │ -    {apply, {supervisor, restart_child, [ch_sup, m1]}}
    │ │ │ │ -   ]}],
    │ │ │ │ - [{"1",
    │ │ │ │ -   [{apply, {supervisor, terminate_child, [ch_sup, m1]}},
    │ │ │ │ -    {apply, {supervisor, delete_child, [ch_sup, m1]}},
    │ │ │ │ -    {update, ch_sup, supervisor}
    │ │ │ │ -   ]}]
    │ │ │ │ -}.

    The order of the instructions is important.

    The supervisor must be registered as ch_sup for the script to work. If the │ │ │ │ +downgrading from "2" to "1":

    {"2",
    │ │ │ │ + [{"1",
    │ │ │ │ +   [{update, ch_sup, supervisor},
    │ │ │ │ +    {apply, {supervisor, restart_child, [ch_sup, m1]}}
    │ │ │ │ +   ]}],
    │ │ │ │ + [{"1",
    │ │ │ │ +   [{apply, {supervisor, terminate_child, [ch_sup, m1]}},
    │ │ │ │ +    {apply, {supervisor, delete_child, [ch_sup, m1]}},
    │ │ │ │ +    {update, ch_sup, supervisor}
    │ │ │ │ +   ]}]
    │ │ │ │ +}.

    The order of the instructions is important.

    The supervisor must be registered as ch_sup for the script to work. If the │ │ │ │ supervisor is not registered, it cannot be accessed directly from the script. │ │ │ │ Instead a help function that finds the pid of the supervisor and calls │ │ │ │ supervisor:restart_child, and so on, must be written. This function is then to │ │ │ │ be called from the script using the apply instruction.

    If the module m1 is introduced in version "2" of ch_app, it must also be │ │ │ │ -loaded when upgrading and deleted when downgrading:

    {"2",
    │ │ │ │ - [{"1",
    │ │ │ │ -   [{add_module, m1},
    │ │ │ │ -    {update, ch_sup, supervisor},
    │ │ │ │ -    {apply, {supervisor, restart_child, [ch_sup, m1]}}
    │ │ │ │ -   ]}],
    │ │ │ │ - [{"1",
    │ │ │ │ -   [{apply, {supervisor, terminate_child, [ch_sup, m1]}},
    │ │ │ │ -    {apply, {supervisor, delete_child, [ch_sup, m1]}},
    │ │ │ │ -    {update, ch_sup, supervisor},
    │ │ │ │ -    {delete_module, m1}
    │ │ │ │ -   ]}]
    │ │ │ │ -}.

    As stated earlier, the order of the instructions is important. When upgrading, │ │ │ │ +loaded when upgrading and deleted when downgrading:

    {"2",
    │ │ │ │ + [{"1",
    │ │ │ │ +   [{add_module, m1},
    │ │ │ │ +    {update, ch_sup, supervisor},
    │ │ │ │ +    {apply, {supervisor, restart_child, [ch_sup, m1]}}
    │ │ │ │ +   ]}],
    │ │ │ │ + [{"1",
    │ │ │ │ +   [{apply, {supervisor, terminate_child, [ch_sup, m1]}},
    │ │ │ │ +    {apply, {supervisor, delete_child, [ch_sup, m1]}},
    │ │ │ │ +    {update, ch_sup, supervisor},
    │ │ │ │ +    {delete_module, m1}
    │ │ │ │ +   ]}]
    │ │ │ │ +}.

    As stated earlier, the order of the instructions is important. When upgrading, │ │ │ │ m1 must be loaded, and the supervisor child specification changed, before the │ │ │ │ new child process can be started. When downgrading, the child process must be │ │ │ │ terminated before the child specification is changed and the module is deleted.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Adding or Deleting a Module │ │ │ │

    │ │ │ │ -

    Example

    A new functional module m is added to ch_app:

    {"2",
    │ │ │ │ - [{"1", [{add_module, m}]}],
    │ │ │ │ - [{"1", [{delete_module, m}]}]

    │ │ │ │ +

    Example

    A new functional module m is added to ch_app:

    {"2",
    │ │ │ │ + [{"1", [{add_module, m}]}],
    │ │ │ │ + [{"1", [{delete_module, m}]}]

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Starting or Terminating a Process │ │ │ │

    │ │ │ │

    In a system structured according to the OTP design principles, any process would │ │ │ │ be a child process belonging to a supervisor, see │ │ │ │ @@ -274,29 +274,29 @@ │ │ │ │ Restarting an Application │ │ │ │ │ │ │ │

    Restarting an application is useful when a change is too complicated to be made │ │ │ │ without restarting the processes, for example, if the supervisor hierarchy has │ │ │ │ been restructured.

    Example

    When adding a child m1 to ch_sup, as in │ │ │ │ Adding and Deleting Child Processes in Changing a │ │ │ │ Supervisor, an alternative to updating the supervisor is to restart the entire │ │ │ │ -application:

    {"2",
    │ │ │ │ - [{"1", [{restart_application, ch_app}]}],
    │ │ │ │ - [{"1", [{restart_application, ch_app}]}]
    │ │ │ │ -}.

    │ │ │ │ +application:

    {"2",
    │ │ │ │ + [{"1", [{restart_application, ch_app}]}],
    │ │ │ │ + [{"1", [{restart_application, ch_app}]}]
    │ │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Changing an Application Specification │ │ │ │

    │ │ │ │

    When installing a release, the application specifications are automatically │ │ │ │ updated before evaluating the relup script. Thus, no instructions are needed │ │ │ │ -in the .appup file:

    {"2",
    │ │ │ │ - [{"1", []}],
    │ │ │ │ - [{"1", []}]
    │ │ │ │ -}.

    │ │ │ │ +in the .appup file:

    {"2",
    │ │ │ │ + [{"1", []}],
    │ │ │ │ + [{"1", []}]
    │ │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Changing Application Configuration │ │ │ │

    │ │ │ │

    Changing an application configuration by updating the env key in the .app │ │ │ │ file is an instance of changing an application specification, see the previous │ │ │ │ @@ -311,26 +311,26 @@ │ │ │ │ applications apply to primary applications only. There are no corresponding │ │ │ │ instructions for included applications. However, since an included application │ │ │ │ is really a supervision tree with a topmost supervisor, started as a child │ │ │ │ process to a supervisor in the including application, a .relup file can be │ │ │ │ manually created.

    Example

    Assume there is a release containing an application prim_app, which │ │ │ │ have a supervisor prim_sup in its supervision tree.

    In a new version of the release, the application ch_app is to be included in │ │ │ │ prim_app. That is, its topmost supervisor ch_sup is to be started as a child │ │ │ │ -process to prim_sup.

    The workflow is as follows:

    Step 1) Edit the code for prim_sup:

    init(...) ->
    │ │ │ │ -    {ok, {...supervisor flags...,
    │ │ │ │ -          [...,
    │ │ │ │ -           {ch_sup, {ch_sup,start_link,[]},
    │ │ │ │ -            permanent,infinity,supervisor,[ch_sup]},
    │ │ │ │ -           ...]}}.

    Step 2) Edit the .app file for prim_app:

    {application, prim_app,
    │ │ │ │ - [...,
    │ │ │ │ -  {vsn, "2"},
    │ │ │ │ +process to prim_sup.

    The workflow is as follows:

    Step 1) Edit the code for prim_sup:

    init(...) ->
    │ │ │ │ +    {ok, {...supervisor flags...,
    │ │ │ │ +          [...,
    │ │ │ │ +           {ch_sup, {ch_sup,start_link,[]},
    │ │ │ │ +            permanent,infinity,supervisor,[ch_sup]},
    │ │ │ │ +           ...]}}.

    Step 2) Edit the .app file for prim_app:

    {application, prim_app,
    │ │ │ │ + [...,
    │ │ │ │ +  {vsn, "2"},
    │ │ │ │    ...,
    │ │ │ │ -  {included_applications, [ch_app]},
    │ │ │ │ +  {included_applications, [ch_app]},
    │ │ │ │    ...
    │ │ │ │ - ]}.

    Step 3) Create a new .rel file, including ch_app:

    {release,
    │ │ │ │ + ]}.

    Step 3) Create a new .rel file, including ch_app:

    {release,
    │ │ │ │   ...,
    │ │ │ │   [...,
    │ │ │ │    {prim_app, "2"},
    │ │ │ │    {ch_app, "1"}]}.

    The included application can be started in two ways. This is described in the │ │ │ │ next two sections.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -385,74 +385,74 @@ │ │ │ │

    Step 4b) Another way to start the included application (or stop it in the case │ │ │ │ of downgrade) is by combining instructions for adding and removing child │ │ │ │ processes to/from prim_sup with instructions for loading/unloading all │ │ │ │ ch_app code and its application specification.

    Again, the .relup file is created manually, either from scratch or by editing a │ │ │ │ generated version. Load all code for ch_app first, and also load the │ │ │ │ application specification, before prim_sup is updated. When downgrading, │ │ │ │ prim_sup is to updated first, before the code for ch_app and its application │ │ │ │ -specification are unloaded.

    {"B",
    │ │ │ │ - [{"A",
    │ │ │ │ -   [],
    │ │ │ │ -   [{load_object_code,{ch_app,"1",[ch_sup,ch3]}},
    │ │ │ │ -    {load_object_code,{prim_app,"2",[prim_sup]}},
    │ │ │ │ +specification are unloaded.

    {"B",
    │ │ │ │ + [{"A",
    │ │ │ │ +   [],
    │ │ │ │ +   [{load_object_code,{ch_app,"1",[ch_sup,ch3]}},
    │ │ │ │ +    {load_object_code,{prim_app,"2",[prim_sup]}},
    │ │ │ │      point_of_no_return,
    │ │ │ │ -    {load,{ch_sup,brutal_purge,brutal_purge}},
    │ │ │ │ -    {load,{ch3,brutal_purge,brutal_purge}},
    │ │ │ │ -    {apply,{application,load,[ch_app]}},
    │ │ │ │ -    {suspend,[prim_sup]},
    │ │ │ │ -    {load,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ │ -    {code_change,up,[{prim_sup,[]}]},
    │ │ │ │ -    {resume,[prim_sup]},
    │ │ │ │ -    {apply,{supervisor,restart_child,[prim_sup,ch_sup]}}]}],
    │ │ │ │ - [{"A",
    │ │ │ │ -   [],
    │ │ │ │ -   [{load_object_code,{prim_app,"1",[prim_sup]}},
    │ │ │ │ +    {load,{ch_sup,brutal_purge,brutal_purge}},
    │ │ │ │ +    {load,{ch3,brutal_purge,brutal_purge}},
    │ │ │ │ +    {apply,{application,load,[ch_app]}},
    │ │ │ │ +    {suspend,[prim_sup]},
    │ │ │ │ +    {load,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ │ +    {code_change,up,[{prim_sup,[]}]},
    │ │ │ │ +    {resume,[prim_sup]},
    │ │ │ │ +    {apply,{supervisor,restart_child,[prim_sup,ch_sup]}}]}],
    │ │ │ │ + [{"A",
    │ │ │ │ +   [],
    │ │ │ │ +   [{load_object_code,{prim_app,"1",[prim_sup]}},
    │ │ │ │      point_of_no_return,
    │ │ │ │ -    {apply,{supervisor,terminate_child,[prim_sup,ch_sup]}},
    │ │ │ │ -    {apply,{supervisor,delete_child,[prim_sup,ch_sup]}},
    │ │ │ │ -    {suspend,[prim_sup]},
    │ │ │ │ -    {load,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ │ -    {code_change,down,[{prim_sup,[]}]},
    │ │ │ │ -    {resume,[prim_sup]},
    │ │ │ │ -    {remove,{ch_sup,brutal_purge,brutal_purge}},
    │ │ │ │ -    {remove,{ch3,brutal_purge,brutal_purge}},
    │ │ │ │ -    {purge,[ch_sup,ch3]},
    │ │ │ │ -    {apply,{application,unload,[ch_app]}}]}]
    │ │ │ │ -}.

    │ │ │ │ + {apply,{supervisor,terminate_child,[prim_sup,ch_sup]}}, │ │ │ │ + {apply,{supervisor,delete_child,[prim_sup,ch_sup]}}, │ │ │ │ + {suspend,[prim_sup]}, │ │ │ │ + {load,{prim_sup,brutal_purge,brutal_purge}}, │ │ │ │ + {code_change,down,[{prim_sup,[]}]}, │ │ │ │ + {resume,[prim_sup]}, │ │ │ │ + {remove,{ch_sup,brutal_purge,brutal_purge}}, │ │ │ │ + {remove,{ch3,brutal_purge,brutal_purge}}, │ │ │ │ + {purge,[ch_sup,ch3]}, │ │ │ │ + {apply,{application,unload,[ch_app]}}]}] │ │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Changing Non-Erlang Code │ │ │ │

    │ │ │ │

    Changing code for a program written in another programming language than Erlang, │ │ │ │ for example, a port program, is application-dependent and OTP provides no │ │ │ │ special support.

    Example

    When changing code for a port program, assume that the Erlang process │ │ │ │ controlling the port is a gen_server portc and that the port is opened in │ │ │ │ -the callback function init/1:

    init(...) ->
    │ │ │ │ +the callback function init/1:

    init(...) ->
    │ │ │ │      ...,
    │ │ │ │ -    PortPrg = filename:join(code:priv_dir(App), "portc"),
    │ │ │ │ -    Port = open_port({spawn,PortPrg}, [...]),
    │ │ │ │ +    PortPrg = filename:join(code:priv_dir(App), "portc"),
    │ │ │ │ +    Port = open_port({spawn,PortPrg}, [...]),
    │ │ │ │      ...,
    │ │ │ │ -    {ok, #state{port=Port, ...}}.

    If the port program is to be updated, the code for the gen_server can be │ │ │ │ + {ok, #state{port=Port, ...}}.

    If the port program is to be updated, the code for the gen_server can be │ │ │ │ extended with a code_change/3 function, which closes the old port and opens a │ │ │ │ new port. (If necessary, the gen_server can first request data that must be │ │ │ │ -saved from the port program and pass this data to the new port):

    code_change(_OldVsn, State, port) ->
    │ │ │ │ +saved from the port program and pass this data to the new port):

    code_change(_OldVsn, State, port) ->
    │ │ │ │      State#state.port ! close,
    │ │ │ │      receive
    │ │ │ │ -        {Port,close} ->
    │ │ │ │ +        {Port,close} ->
    │ │ │ │              true
    │ │ │ │      end,
    │ │ │ │ -    PortPrg = filename:join(code:priv_dir(App), "portc"),
    │ │ │ │ -    Port = open_port({spawn,PortPrg}, [...]),
    │ │ │ │ -    {ok, #state{port=Port, ...}}.

    Update the application version number in the .app file and write an .appup │ │ │ │ -file:

    ["2",
    │ │ │ │ - [{"1", [{update, portc, {advanced,port}}]}],
    │ │ │ │ - [{"1", [{update, portc, {advanced,port}}]}]
    │ │ │ │ -].

    Ensure that the priv directory, where the C program is located, is included in │ │ │ │ -the new release package:

    1> systools:make_tar("my_release", [{dirs,[priv]}]).
    │ │ │ │ +    PortPrg = filename:join(code:priv_dir(App), "portc"),
    │ │ │ │ +    Port = open_port({spawn,PortPrg}, [...]),
    │ │ │ │ +    {ok, #state{port=Port, ...}}.

    Update the application version number in the .app file and write an .appup │ │ │ │ +file:

    ["2",
    │ │ │ │ + [{"1", [{update, portc, {advanced,port}}]}],
    │ │ │ │ + [{"1", [{update, portc, {advanced,port}}]}]
    │ │ │ │ +].

    Ensure that the priv directory, where the C program is located, is included in │ │ │ │ +the new release package:

    1> systools:make_tar("my_release", [{dirs,[priv]}]).
    │ │ │ │  ...

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Runtime System Restart and Upgrade │ │ │ │

    │ │ │ │

    Two upgrade instructions restart the runtime system:

    • restart_new_emulator

      Intended when ERTS, Kernel, STDLIB, or SASL is upgraded. It is automatically │ │ │ │ @@ -460,20 +460,20 @@ │ │ │ │ executed before all other upgrade instructions. For more information about │ │ │ │ this instruction, see restart_new_emulator (Low-Level) in │ │ │ │ Release Handling Instructions.

    • restart_emulator

      Used when a restart of the runtime system is required after all other upgrade │ │ │ │ instructions are executed. For more information about this instruction, see │ │ │ │ restart_emulator (Low-Level) in │ │ │ │ Release Handling Instructions.

    If a runtime system restart is necessary and no upgrade instructions are needed, │ │ │ │ that is, if the restart itself is enough for the upgraded applications to start │ │ │ │ -running the new versions, a simple .relup file can be created manually:

    {"B",
    │ │ │ │ - [{"A",
    │ │ │ │ -   [],
    │ │ │ │ -   [restart_emulator]}],
    │ │ │ │ - [{"A",
    │ │ │ │ -   [],
    │ │ │ │ -   [restart_emulator]}]
    │ │ │ │ -}.

    In this case, the release handler framework with automatic packing and unpacking │ │ │ │ +running the new versions, a simple .relup file can be created manually:

    {"B",
    │ │ │ │ + [{"A",
    │ │ │ │ +   [],
    │ │ │ │ +   [restart_emulator]}],
    │ │ │ │ + [{"A",
    │ │ │ │ +   [],
    │ │ │ │ +   [restart_emulator]}]
    │ │ │ │ +}.

    In this case, the release handler framework with automatic packing and unpacking │ │ │ │ of release packages, automatic path updates, and so on, can be used without │ │ │ │ having to specify .appup files.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/applications.xhtml │ │ │ │ @@ -40,34 +40,34 @@ │ │ │ │ directory structure.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Application Callback Module │ │ │ │

    │ │ │ │

    How to start and stop the code for the application, including its supervision │ │ │ │ -tree, is described by two callback functions:

    start(StartType, StartArgs) -> {ok, Pid} | {ok, Pid, State}
    │ │ │ │ -stop(State)
    • start/2 is called when starting the application and is to create the │ │ │ │ +tree, is described by two callback functions:

      start(StartType, StartArgs) -> {ok, Pid} | {ok, Pid, State}
      │ │ │ │ +stop(State)
      • start/2 is called when starting the application and is to create the │ │ │ │ supervision tree by starting the top supervisor. It is expected to return the │ │ │ │ pid of the top supervisor and an optional term, State, which defaults to │ │ │ │ []. This term is passed as is to stop/1.
      • StartType is usually the atom normal. It has other values only in the case │ │ │ │ of a takeover or failover; see │ │ │ │ Distributed Applications.
      • StartArgs is defined by the key mod in the │ │ │ │ application resource file.
      • stop/1 is called after the application has been stopped and is to do any │ │ │ │ necessary cleaning up. The actual stopping of the application, that is, │ │ │ │ shutting down the supervision tree, is handled automatically as described in │ │ │ │ Starting and Stopping Applications.

      Example of an application callback module for packaging the supervision tree │ │ │ │ -from Supervisor Behaviour:

      -module(ch_app).
      │ │ │ │ --behaviour(application).
      │ │ │ │ +from Supervisor Behaviour:

      -module(ch_app).
      │ │ │ │ +-behaviour(application).
      │ │ │ │  
      │ │ │ │ --export([start/2, stop/1]).
      │ │ │ │ +-export([start/2, stop/1]).
      │ │ │ │  
      │ │ │ │ -start(_Type, _Args) ->
      │ │ │ │ -    ch_sup:start_link().
      │ │ │ │ +start(_Type, _Args) ->
      │ │ │ │ +    ch_sup:start_link().
      │ │ │ │  
      │ │ │ │ -stop(_State) ->
      │ │ │ │ +stop(_State) ->
      │ │ │ │      ok.

      A library application that cannot be started or stopped does not need any │ │ │ │ application callback module.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Application Resource File │ │ │ │

      │ │ │ │ @@ -78,22 +78,22 @@ │ │ │ │ keys.

    The contents of a minimal .app file for a library application libapp looks │ │ │ │ as follows:

    {application, libapp, []}.

    The contents of a minimal .app file ch_app.app for a supervision tree │ │ │ │ application like ch_app looks as follows:

    {application, ch_app,
    │ │ │ │   [{mod, {ch_app,[]}}]}.

    The key mod defines the callback module and start argument of the application, │ │ │ │ in this case ch_app and [], respectively. This means that the following is │ │ │ │ called when the application is to be started:

    ch_app:start(normal, [])

    The following is called when the application is stopped:

    ch_app:stop([])

    When using systools, the Erlang/OTP tools for packaging code (see Section │ │ │ │ Releases), the keys description, vsn, modules, │ │ │ │ -registered, and applications are also to be specified:

    {application, ch_app,
    │ │ │ │ - [{description, "Channel allocator"},
    │ │ │ │ -  {vsn, "1"},
    │ │ │ │ -  {modules, [ch_app, ch_sup, ch3]},
    │ │ │ │ -  {registered, [ch3]},
    │ │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ │ -  {mod, {ch_app,[]}}
    │ │ │ │ - ]}.
    • description - A short description, a string. Defaults to "".
    • vsn - Version number, a string. Defaults to "".
    • modules - All modules introduced by this application. systools uses │ │ │ │ +registered, and applications are also to be specified:

      {application, ch_app,
      │ │ │ │ + [{description, "Channel allocator"},
      │ │ │ │ +  {vsn, "1"},
      │ │ │ │ +  {modules, [ch_app, ch_sup, ch3]},
      │ │ │ │ +  {registered, [ch3]},
      │ │ │ │ +  {applications, [kernel, stdlib, sasl]},
      │ │ │ │ +  {mod, {ch_app,[]}}
      │ │ │ │ + ]}.
      • description - A short description, a string. Defaults to "".
      • vsn - Version number, a string. Defaults to "".
      • modules - All modules introduced by this application. systools uses │ │ │ │ this list when generating boot scripts and tar files. A module must only │ │ │ │ be included in one application. Defaults to [].
      • registered - All names of registered processes in the application. │ │ │ │ systools uses this list to detect name clashes between applications. │ │ │ │ Defaults to [].
      • applications - All applications that must be started before this │ │ │ │ application is started. systools uses this list to generate correct boot │ │ │ │ scripts. Defaults to []. Notice that all applications have dependencies to │ │ │ │ at least Kernel and STDLIB.

      Note

      For details about the syntax and contents of the application resource file, │ │ │ │ @@ -205,38 +205,38 @@ │ │ │ │ stop applications.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Loading and Unloading Applications │ │ │ │

      │ │ │ │

      Before an application can be started, it must be loaded. The application │ │ │ │ -controller reads and stores the information from the .app file:

      1> application:load(ch_app).
      │ │ │ │ +controller reads and stores the information from the .app file:

      1> application:load(ch_app).
      │ │ │ │  ok
      │ │ │ │ -2> application:loaded_applications().
      │ │ │ │ -[{kernel,"ERTS  CXC 138 10","2.8.1.3"},
      │ │ │ │ - {stdlib,"ERTS  CXC 138 10","1.11.4.3"},
      │ │ │ │ - {ch_app,"Channel allocator","1"}]

      An application that has been stopped, or has never been started, can be │ │ │ │ +2> application:loaded_applications(). │ │ │ │ +[{kernel,"ERTS CXC 138 10","2.8.1.3"}, │ │ │ │ + {stdlib,"ERTS CXC 138 10","1.11.4.3"}, │ │ │ │ + {ch_app,"Channel allocator","1"}]

      An application that has been stopped, or has never been started, can be │ │ │ │ unloaded. The information about the application is erased from the internal │ │ │ │ -database of the application controller.

      3> application:unload(ch_app).
      │ │ │ │ +database of the application controller.

      3> application:unload(ch_app).
      │ │ │ │  ok
      │ │ │ │ -4> application:loaded_applications().
      │ │ │ │ -[{kernel,"ERTS  CXC 138 10","2.8.1.3"},
      │ │ │ │ - {stdlib,"ERTS  CXC 138 10","1.11.4.3"}]

      Note

      Loading/unloading an application does not load/unload the code used by the │ │ │ │ +4> application:loaded_applications(). │ │ │ │ +[{kernel,"ERTS CXC 138 10","2.8.1.3"}, │ │ │ │ + {stdlib,"ERTS CXC 138 10","1.11.4.3"}]

      Note

      Loading/unloading an application does not load/unload the code used by the │ │ │ │ application. Code loading is handled in the usual way by the code server.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Starting and Stopping Applications │ │ │ │

      │ │ │ │ -

      An application is started by calling:

      5> application:start(ch_app).
      │ │ │ │ +

      An application is started by calling:

      5> application:start(ch_app).
      │ │ │ │  ok
      │ │ │ │ -6> application:which_applications().
      │ │ │ │ -[{kernel,"ERTS  CXC 138 10","2.8.1.3"},
      │ │ │ │ - {stdlib,"ERTS  CXC 138 10","1.11.4.3"},
      │ │ │ │ - {ch_app,"Channel allocator","1"}]

      If the application is not already loaded, the application controller first loads │ │ │ │ +6> application:which_applications(). │ │ │ │ +[{kernel,"ERTS CXC 138 10","2.8.1.3"}, │ │ │ │ + {stdlib,"ERTS CXC 138 10","1.11.4.3"}, │ │ │ │ + {ch_app,"Channel allocator","1"}]

      If the application is not already loaded, the application controller first loads │ │ │ │ it using application:load/1. It checks the value of the applications key to │ │ │ │ ensure that all applications that are to be started before this application are │ │ │ │ running.

      Following that, the application controller creates an application master for │ │ │ │ the application.

      The application master establishes itself as the group │ │ │ │ leader of all processes in the application │ │ │ │ and will forward I/O to the previous group leader.

      Note

      The purpose of the application master being the group leader is to easily │ │ │ │ keep track of which processes that belong to the application. That is needed │ │ │ │ @@ -252,55 +252,55 @@ │ │ │ │ defined by the mod key.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Configuring an Application │ │ │ │

      │ │ │ │

      An application can be configured using configuration parameters. These are a │ │ │ │ -list of {Par,Val} tuples specified by a key env in the .app file:

      {application, ch_app,
      │ │ │ │ - [{description, "Channel allocator"},
      │ │ │ │ -  {vsn, "1"},
      │ │ │ │ -  {modules, [ch_app, ch_sup, ch3]},
      │ │ │ │ -  {registered, [ch3]},
      │ │ │ │ -  {applications, [kernel, stdlib, sasl]},
      │ │ │ │ -  {mod, {ch_app,[]}},
      │ │ │ │ -  {env, [{file, "/usr/local/log"}]}
      │ │ │ │ - ]}.

      Par is to be an atom. Val is any term. The application can retrieve the │ │ │ │ +list of {Par,Val} tuples specified by a key env in the .app file:

      {application, ch_app,
      │ │ │ │ + [{description, "Channel allocator"},
      │ │ │ │ +  {vsn, "1"},
      │ │ │ │ +  {modules, [ch_app, ch_sup, ch3]},
      │ │ │ │ +  {registered, [ch3]},
      │ │ │ │ +  {applications, [kernel, stdlib, sasl]},
      │ │ │ │ +  {mod, {ch_app,[]}},
      │ │ │ │ +  {env, [{file, "/usr/local/log"}]}
      │ │ │ │ + ]}.

      Par is to be an atom. Val is any term. The application can retrieve the │ │ │ │ value of a configuration parameter by calling application:get_env(App, Par) or │ │ │ │ a number of similar functions. For more information, see module application │ │ │ │ in Kernel.

      Example:

      % erl
      │ │ │ │ -Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
      │ │ │ │ +Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
      │ │ │ │  
      │ │ │ │ -Eshell V5.2.3.6  (abort with ^G)
      │ │ │ │ -1> application:start(ch_app).
      │ │ │ │ +Eshell V5.2.3.6  (abort with ^G)
      │ │ │ │ +1> application:start(ch_app).
      │ │ │ │  ok
      │ │ │ │ -2> application:get_env(ch_app, file).
      │ │ │ │ -{ok,"/usr/local/log"}

      The values in the .app file can be overridden by values in a system │ │ │ │ +2> application:get_env(ch_app, file). │ │ │ │ +{ok,"/usr/local/log"}

    The values in the .app file can be overridden by values in a system │ │ │ │ configuration file. This is a file that contains configuration parameters for │ │ │ │ -relevant applications:

    [{Application1, [{Par11,Val11},...]},
    │ │ │ │ +relevant applications:

    [{Application1, [{Par11,Val11},...]},
    │ │ │ │   ...,
    │ │ │ │ - {ApplicationN, [{ParN1,ValN1},...]}].

    The system configuration is to be called Name.config and Erlang is to be │ │ │ │ + {ApplicationN, [{ParN1,ValN1},...]}].

    The system configuration is to be called Name.config and Erlang is to be │ │ │ │ started with the command-line argument -config Name. For details, see │ │ │ │ config in Kernel.

    Example:

    A file test.config is created with the following contents:

    [{ch_app, [{file, "testlog"}]}].

    The value of file overrides the value of file as defined in the .app file:

    % erl -config test
    │ │ │ │ -Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
    │ │ │ │ +Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
    │ │ │ │  
    │ │ │ │ -Eshell V5.2.3.6  (abort with ^G)
    │ │ │ │ -1> application:start(ch_app).
    │ │ │ │ +Eshell V5.2.3.6  (abort with ^G)
    │ │ │ │ +1> application:start(ch_app).
    │ │ │ │  ok
    │ │ │ │ -2> application:get_env(ch_app, file).
    │ │ │ │ -{ok,"testlog"}

    If release handling is used, exactly one system │ │ │ │ +2> application:get_env(ch_app, file). │ │ │ │ +{ok,"testlog"}

    If release handling is used, exactly one system │ │ │ │ configuration file is to be used and that file is to be called sys.config.

    The values in the .app file and the values in a system configuration file can │ │ │ │ be overridden directly from the command line:

    % erl -ApplName Par1 Val1 ... ParN ValN

    Example:

    % erl -ch_app file '"testlog"'
    │ │ │ │ -Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
    │ │ │ │ +Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
    │ │ │ │  
    │ │ │ │ -Eshell V5.2.3.6  (abort with ^G)
    │ │ │ │ -1> application:start(ch_app).
    │ │ │ │ +Eshell V5.2.3.6  (abort with ^G)
    │ │ │ │ +1> application:start(ch_app).
    │ │ │ │  ok
    │ │ │ │ -2> application:get_env(ch_app, file).
    │ │ │ │ -{ok,"testlog"}

    │ │ │ │ +2> application:get_env(ch_app, file). │ │ │ │ +{ok,"testlog"}

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Application Start Types │ │ │ │

    │ │ │ │

    A start type is defined when starting the application:

    application:start(Application, Type)

    application:start(Application) is the same as calling │ │ │ │ application:start(Application, temporary). The type can also be permanent or │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/applications.html │ │ │ @@ -135,34 +135,34 @@ │ │ │ directory structure.

    │ │ │ │ │ │ │ │ │ │ │ │ Application Callback Module │ │ │

    │ │ │

    How to start and stop the code for the application, including its supervision │ │ │ -tree, is described by two callback functions:

    start(StartType, StartArgs) -> {ok, Pid} | {ok, Pid, State}
    │ │ │ -stop(State)
    • start/2 is called when starting the application and is to create the │ │ │ +tree, is described by two callback functions:

      start(StartType, StartArgs) -> {ok, Pid} | {ok, Pid, State}
      │ │ │ +stop(State)
      • start/2 is called when starting the application and is to create the │ │ │ supervision tree by starting the top supervisor. It is expected to return the │ │ │ pid of the top supervisor and an optional term, State, which defaults to │ │ │ []. This term is passed as is to stop/1.
      • StartType is usually the atom normal. It has other values only in the case │ │ │ of a takeover or failover; see │ │ │ Distributed Applications.
      • StartArgs is defined by the key mod in the │ │ │ application resource file.
      • stop/1 is called after the application has been stopped and is to do any │ │ │ necessary cleaning up. The actual stopping of the application, that is, │ │ │ shutting down the supervision tree, is handled automatically as described in │ │ │ Starting and Stopping Applications.

      Example of an application callback module for packaging the supervision tree │ │ │ -from Supervisor Behaviour:

      -module(ch_app).
      │ │ │ --behaviour(application).
      │ │ │ +from Supervisor Behaviour:

      -module(ch_app).
      │ │ │ +-behaviour(application).
      │ │ │  
      │ │ │ --export([start/2, stop/1]).
      │ │ │ +-export([start/2, stop/1]).
      │ │ │  
      │ │ │ -start(_Type, _Args) ->
      │ │ │ -    ch_sup:start_link().
      │ │ │ +start(_Type, _Args) ->
      │ │ │ +    ch_sup:start_link().
      │ │ │  
      │ │ │ -stop(_State) ->
      │ │ │ +stop(_State) ->
      │ │ │      ok.

      A library application that cannot be started or stopped does not need any │ │ │ application callback module.

      │ │ │ │ │ │ │ │ │ │ │ │ Application Resource File │ │ │

      │ │ │ @@ -173,22 +173,22 @@ │ │ │ keys.

    The contents of a minimal .app file for a library application libapp looks │ │ │ as follows:

    {application, libapp, []}.

    The contents of a minimal .app file ch_app.app for a supervision tree │ │ │ application like ch_app looks as follows:

    {application, ch_app,
    │ │ │   [{mod, {ch_app,[]}}]}.

    The key mod defines the callback module and start argument of the application, │ │ │ in this case ch_app and [], respectively. This means that the following is │ │ │ called when the application is to be started:

    ch_app:start(normal, [])

    The following is called when the application is stopped:

    ch_app:stop([])

    When using systools, the Erlang/OTP tools for packaging code (see Section │ │ │ Releases), the keys description, vsn, modules, │ │ │ -registered, and applications are also to be specified:

    {application, ch_app,
    │ │ │ - [{description, "Channel allocator"},
    │ │ │ -  {vsn, "1"},
    │ │ │ -  {modules, [ch_app, ch_sup, ch3]},
    │ │ │ -  {registered, [ch3]},
    │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ -  {mod, {ch_app,[]}}
    │ │ │ - ]}.
    • description - A short description, a string. Defaults to "".
    • vsn - Version number, a string. Defaults to "".
    • modules - All modules introduced by this application. systools uses │ │ │ +registered, and applications are also to be specified:

      {application, ch_app,
      │ │ │ + [{description, "Channel allocator"},
      │ │ │ +  {vsn, "1"},
      │ │ │ +  {modules, [ch_app, ch_sup, ch3]},
      │ │ │ +  {registered, [ch3]},
      │ │ │ +  {applications, [kernel, stdlib, sasl]},
      │ │ │ +  {mod, {ch_app,[]}}
      │ │ │ + ]}.
      • description - A short description, a string. Defaults to "".
      • vsn - Version number, a string. Defaults to "".
      • modules - All modules introduced by this application. systools uses │ │ │ this list when generating boot scripts and tar files. A module must only │ │ │ be included in one application. Defaults to [].
      • registered - All names of registered processes in the application. │ │ │ systools uses this list to detect name clashes between applications. │ │ │ Defaults to [].
      • applications - All applications that must be started before this │ │ │ application is started. systools uses this list to generate correct boot │ │ │ scripts. Defaults to []. Notice that all applications have dependencies to │ │ │ at least Kernel and STDLIB.

      Note

      For details about the syntax and contents of the application resource file, │ │ │ @@ -300,38 +300,38 @@ │ │ │ stop applications.

      │ │ │ │ │ │ │ │ │ │ │ │ Loading and Unloading Applications │ │ │

      │ │ │

      Before an application can be started, it must be loaded. The application │ │ │ -controller reads and stores the information from the .app file:

      1> application:load(ch_app).
      │ │ │ +controller reads and stores the information from the .app file:

      1> application:load(ch_app).
      │ │ │  ok
      │ │ │ -2> application:loaded_applications().
      │ │ │ -[{kernel,"ERTS  CXC 138 10","2.8.1.3"},
      │ │ │ - {stdlib,"ERTS  CXC 138 10","1.11.4.3"},
      │ │ │ - {ch_app,"Channel allocator","1"}]

      An application that has been stopped, or has never been started, can be │ │ │ +2> application:loaded_applications(). │ │ │ +[{kernel,"ERTS CXC 138 10","2.8.1.3"}, │ │ │ + {stdlib,"ERTS CXC 138 10","1.11.4.3"}, │ │ │ + {ch_app,"Channel allocator","1"}]

      An application that has been stopped, or has never been started, can be │ │ │ unloaded. The information about the application is erased from the internal │ │ │ -database of the application controller.

      3> application:unload(ch_app).
      │ │ │ +database of the application controller.

      3> application:unload(ch_app).
      │ │ │  ok
      │ │ │ -4> application:loaded_applications().
      │ │ │ -[{kernel,"ERTS  CXC 138 10","2.8.1.3"},
      │ │ │ - {stdlib,"ERTS  CXC 138 10","1.11.4.3"}]

      Note

      Loading/unloading an application does not load/unload the code used by the │ │ │ +4> application:loaded_applications(). │ │ │ +[{kernel,"ERTS CXC 138 10","2.8.1.3"}, │ │ │ + {stdlib,"ERTS CXC 138 10","1.11.4.3"}]

      Note

      Loading/unloading an application does not load/unload the code used by the │ │ │ application. Code loading is handled in the usual way by the code server.

      │ │ │ │ │ │ │ │ │ │ │ │ Starting and Stopping Applications │ │ │

      │ │ │ -

      An application is started by calling:

      5> application:start(ch_app).
      │ │ │ +

      An application is started by calling:

      5> application:start(ch_app).
      │ │ │  ok
      │ │ │ -6> application:which_applications().
      │ │ │ -[{kernel,"ERTS  CXC 138 10","2.8.1.3"},
      │ │ │ - {stdlib,"ERTS  CXC 138 10","1.11.4.3"},
      │ │ │ - {ch_app,"Channel allocator","1"}]

      If the application is not already loaded, the application controller first loads │ │ │ +6> application:which_applications(). │ │ │ +[{kernel,"ERTS CXC 138 10","2.8.1.3"}, │ │ │ + {stdlib,"ERTS CXC 138 10","1.11.4.3"}, │ │ │ + {ch_app,"Channel allocator","1"}]

      If the application is not already loaded, the application controller first loads │ │ │ it using application:load/1. It checks the value of the applications key to │ │ │ ensure that all applications that are to be started before this application are │ │ │ running.

      Following that, the application controller creates an application master for │ │ │ the application.

      The application master establishes itself as the group │ │ │ leader of all processes in the application │ │ │ and will forward I/O to the previous group leader.

      Note

      The purpose of the application master being the group leader is to easily │ │ │ keep track of which processes that belong to the application. That is needed │ │ │ @@ -347,55 +347,55 @@ │ │ │ defined by the mod key.

      │ │ │ │ │ │ │ │ │ │ │ │ Configuring an Application │ │ │

      │ │ │

      An application can be configured using configuration parameters. These are a │ │ │ -list of {Par,Val} tuples specified by a key env in the .app file:

      {application, ch_app,
      │ │ │ - [{description, "Channel allocator"},
      │ │ │ -  {vsn, "1"},
      │ │ │ -  {modules, [ch_app, ch_sup, ch3]},
      │ │ │ -  {registered, [ch3]},
      │ │ │ -  {applications, [kernel, stdlib, sasl]},
      │ │ │ -  {mod, {ch_app,[]}},
      │ │ │ -  {env, [{file, "/usr/local/log"}]}
      │ │ │ - ]}.

      Par is to be an atom. Val is any term. The application can retrieve the │ │ │ +list of {Par,Val} tuples specified by a key env in the .app file:

      {application, ch_app,
      │ │ │ + [{description, "Channel allocator"},
      │ │ │ +  {vsn, "1"},
      │ │ │ +  {modules, [ch_app, ch_sup, ch3]},
      │ │ │ +  {registered, [ch3]},
      │ │ │ +  {applications, [kernel, stdlib, sasl]},
      │ │ │ +  {mod, {ch_app,[]}},
      │ │ │ +  {env, [{file, "/usr/local/log"}]}
      │ │ │ + ]}.

      Par is to be an atom. Val is any term. The application can retrieve the │ │ │ value of a configuration parameter by calling application:get_env(App, Par) or │ │ │ a number of similar functions. For more information, see module application │ │ │ in Kernel.

      Example:

      % erl
      │ │ │ -Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
      │ │ │ +Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
      │ │ │  
      │ │ │ -Eshell V5.2.3.6  (abort with ^G)
      │ │ │ -1> application:start(ch_app).
      │ │ │ +Eshell V5.2.3.6  (abort with ^G)
      │ │ │ +1> application:start(ch_app).
      │ │ │  ok
      │ │ │ -2> application:get_env(ch_app, file).
      │ │ │ -{ok,"/usr/local/log"}

      The values in the .app file can be overridden by values in a system │ │ │ +2> application:get_env(ch_app, file). │ │ │ +{ok,"/usr/local/log"}

    The values in the .app file can be overridden by values in a system │ │ │ configuration file. This is a file that contains configuration parameters for │ │ │ -relevant applications:

    [{Application1, [{Par11,Val11},...]},
    │ │ │ +relevant applications:

    [{Application1, [{Par11,Val11},...]},
    │ │ │   ...,
    │ │ │ - {ApplicationN, [{ParN1,ValN1},...]}].

    The system configuration is to be called Name.config and Erlang is to be │ │ │ + {ApplicationN, [{ParN1,ValN1},...]}].

    The system configuration is to be called Name.config and Erlang is to be │ │ │ started with the command-line argument -config Name. For details, see │ │ │ config in Kernel.

    Example:

    A file test.config is created with the following contents:

    [{ch_app, [{file, "testlog"}]}].

    The value of file overrides the value of file as defined in the .app file:

    % erl -config test
    │ │ │ -Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
    │ │ │ +Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
    │ │ │  
    │ │ │ -Eshell V5.2.3.6  (abort with ^G)
    │ │ │ -1> application:start(ch_app).
    │ │ │ +Eshell V5.2.3.6  (abort with ^G)
    │ │ │ +1> application:start(ch_app).
    │ │ │  ok
    │ │ │ -2> application:get_env(ch_app, file).
    │ │ │ -{ok,"testlog"}

    If release handling is used, exactly one system │ │ │ +2> application:get_env(ch_app, file). │ │ │ +{ok,"testlog"}

    If release handling is used, exactly one system │ │ │ configuration file is to be used and that file is to be called sys.config.

    The values in the .app file and the values in a system configuration file can │ │ │ be overridden directly from the command line:

    % erl -ApplName Par1 Val1 ... ParN ValN

    Example:

    % erl -ch_app file '"testlog"'
    │ │ │ -Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
    │ │ │ +Erlang (BEAM) emulator version 5.2.3.6 [hipe] [threads:0]
    │ │ │  
    │ │ │ -Eshell V5.2.3.6  (abort with ^G)
    │ │ │ -1> application:start(ch_app).
    │ │ │ +Eshell V5.2.3.6  (abort with ^G)
    │ │ │ +1> application:start(ch_app).
    │ │ │  ok
    │ │ │ -2> application:get_env(ch_app, file).
    │ │ │ -{ok,"testlog"}

    │ │ │ +2> application:get_env(ch_app, file). │ │ │ +{ok,"testlog"}

    │ │ │ │ │ │ │ │ │ │ │ │ Application Start Types │ │ │

    │ │ │

    A start type is defined when starting the application:

    application:start(Application, Type)

    application:start(Application) is the same as calling │ │ │ application:start(Application, temporary). The type can also be permanent or │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/appup_cookbook.html │ │ │ @@ -120,18 +120,18 @@ │ │ │ │ │ │ │ │ │ │ │ │ Changing a Functional Module │ │ │ │ │ │

    When a functional module has been changed, for example, if a new function has │ │ │ been added or a bug has been corrected, simple code replacement is sufficient, │ │ │ -for example:

    {"2",
    │ │ │ - [{"1", [{load_module, m}]}],
    │ │ │ - [{"1", [{load_module, m}]}]
    │ │ │ -}.

    │ │ │ +for example:

    {"2",
    │ │ │ + [{"1", [{load_module, m}]}],
    │ │ │ + [{"1", [{load_module, m}]}]
    │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ Changing a Residence Module │ │ │

    │ │ │

    In a system implemented according to the OTP design principles, all processes, │ │ │ except system processes and special processes, reside in one of the behaviours │ │ │ @@ -142,46 +142,46 @@ │ │ │ │ │ │ │ │ │ │ │ │ Changing a Callback Module │ │ │ │ │ │

    A callback module is a functional module, and for code extensions simple code │ │ │ replacement is sufficient.

    Example

    When adding a function to ch3, as described in the example in │ │ │ -Release Handling, ch_app.appup looks as follows:

    {"2",
    │ │ │ - [{"1", [{load_module, ch3}]}],
    │ │ │ - [{"1", [{load_module, ch3}]}]
    │ │ │ -}.

    OTP also supports changing the internal state of behaviour processes; see │ │ │ +Release Handling, ch_app.appup looks as follows:

    {"2",
    │ │ │ + [{"1", [{load_module, ch3}]}],
    │ │ │ + [{"1", [{load_module, ch3}]}]
    │ │ │ +}.

    OTP also supports changing the internal state of behaviour processes; see │ │ │ Changing Internal State.

    │ │ │ │ │ │ │ │ │ │ │ │ Changing Internal State │ │ │

    │ │ │

    In this case, simple code replacement is not sufficient. The process must │ │ │ explicitly transform its state using the callback function code_change/3 before │ │ │ switching to the new version of the callback module. Thus, synchronized code │ │ │ replacement is used.

    Example

    Consider the ch3 module from │ │ │ gen_server Behaviour. The internal state is a term │ │ │ Chs representing the available channels. Assume you want to add a counter N, │ │ │ which keeps track of the number of alloc requests so far. This means that the │ │ │ -format must be changed to {Chs,N}.

    The .appup file can look as follows:

    {"2",
    │ │ │ - [{"1", [{update, ch3, {advanced, []}}]}],
    │ │ │ - [{"1", [{update, ch3, {advanced, []}}]}]
    │ │ │ -}.

    The third element of the update instruction is a tuple {advanced,Extra}, │ │ │ +format must be changed to {Chs,N}.

    The .appup file can look as follows:

    {"2",
    │ │ │ + [{"1", [{update, ch3, {advanced, []}}]}],
    │ │ │ + [{"1", [{update, ch3, {advanced, []}}]}]
    │ │ │ +}.

    The third element of the update instruction is a tuple {advanced,Extra}, │ │ │ which says that the affected processes are to do a state transformation before │ │ │ loading the new version of the module. This is done by the processes calling the │ │ │ callback function code_change/3 (see gen_server in STDLIB). │ │ │ -The term Extra, in this case [], is passed as is to the function:

    -module(ch3).
    │ │ │ +The term Extra, in this case [], is passed as is to the function:

    -module(ch3).
    │ │ │  ...
    │ │ │ --export([code_change/3]).
    │ │ │ +-export([code_change/3]).
    │ │ │  ...
    │ │ │ -code_change({down, _Vsn}, {Chs, N}, _Extra) ->
    │ │ │ -    {ok, Chs};
    │ │ │ -code_change(_Vsn, Chs, _Extra) ->
    │ │ │ -    {ok, {Chs, 0}}.

    The first argument is {down,Vsn} if there is a downgrade, or Vsn if there is │ │ │ +code_change({down, _Vsn}, {Chs, N}, _Extra) -> │ │ │ + {ok, Chs}; │ │ │ +code_change(_Vsn, Chs, _Extra) -> │ │ │ + {ok, {Chs, 0}}.

    The first argument is {down,Vsn} if there is a downgrade, or Vsn if there is │ │ │ a upgrade. The term Vsn is fetched from the 'original' version of the module, │ │ │ that is, the version you are upgrading from, or downgrading to.

    The version is defined by the module attribute vsn, if any. There is no such │ │ │ attribute in ch3, so in this case the version is the checksum (a huge integer) │ │ │ of the beam file, an uninteresting value, which is ignored.

    The other callback functions of ch3 must also be modified and perhaps a new │ │ │ interface function must be added, but this is not shown here.

    │ │ │ │ │ │ │ │ │ @@ -190,67 +190,67 @@ │ │ │

    │ │ │

    Assume that a module is extended by adding an interface function, as in the │ │ │ example in Release Handling, where a function │ │ │ available/0 is added to ch3.

    If a call is added to this function, say in module m1, a runtime error could │ │ │ can occur during release upgrade if the new version of m1 is loaded first and │ │ │ calls ch3:available/0 before the new version of ch3 is loaded.

    Thus, ch3 must be loaded before m1, in the upgrade case, and conversely in │ │ │ the downgrade case. m1 is said to be dependent on ch3. In a release │ │ │ -handling instruction, this is expressed by the DepMods element:

    {load_module, Module, DepMods}
    │ │ │ -{update, Module, {advanced, Extra}, DepMods}

    DepMods is a list of modules, on which Module is dependent.

    Example

    The module m1 in application myapp is dependent on ch3 when │ │ │ +handling instruction, this is expressed by the DepMods element:

    {load_module, Module, DepMods}
    │ │ │ +{update, Module, {advanced, Extra}, DepMods}

    DepMods is a list of modules, on which Module is dependent.

    Example

    The module m1 in application myapp is dependent on ch3 when │ │ │ upgrading from "1" to "2", or downgrading from "2" to "1":

    myapp.appup:
    │ │ │  
    │ │ │ -{"2",
    │ │ │ - [{"1", [{load_module, m1, [ch3]}]}],
    │ │ │ - [{"1", [{load_module, m1, [ch3]}]}]
    │ │ │ -}.
    │ │ │ +{"2",
    │ │ │ + [{"1", [{load_module, m1, [ch3]}]}],
    │ │ │ + [{"1", [{load_module, m1, [ch3]}]}]
    │ │ │ +}.
    │ │ │  
    │ │ │  ch_app.appup:
    │ │ │  
    │ │ │ -{"2",
    │ │ │ - [{"1", [{load_module, ch3}]}],
    │ │ │ - [{"1", [{load_module, ch3}]}]
    │ │ │ -}.

    If instead m1 and ch3 belong to the same application, the .appup file can │ │ │ -look as follows:

    {"2",
    │ │ │ - [{"1",
    │ │ │ -   [{load_module, ch3},
    │ │ │ -    {load_module, m1, [ch3]}]}],
    │ │ │ - [{"1",
    │ │ │ -   [{load_module, ch3},
    │ │ │ -    {load_module, m1, [ch3]}]}]
    │ │ │ -}.

    m1 is dependent on ch3 also when downgrading. systools knows the │ │ │ +{"2", │ │ │ + [{"1", [{load_module, ch3}]}], │ │ │ + [{"1", [{load_module, ch3}]}] │ │ │ +}.

    If instead m1 and ch3 belong to the same application, the .appup file can │ │ │ +look as follows:

    {"2",
    │ │ │ + [{"1",
    │ │ │ +   [{load_module, ch3},
    │ │ │ +    {load_module, m1, [ch3]}]}],
    │ │ │ + [{"1",
    │ │ │ +   [{load_module, ch3},
    │ │ │ +    {load_module, m1, [ch3]}]}]
    │ │ │ +}.

    m1 is dependent on ch3 also when downgrading. systools knows the │ │ │ difference between up- and downgrading and generates a correct relup, where │ │ │ ch3 is loaded before m1 when upgrading, but m1 is loaded before ch3 when │ │ │ downgrading.

    │ │ │ │ │ │ │ │ │ │ │ │ Changing Code for a Special Process │ │ │

    │ │ │

    In this case, simple code replacement is not sufficient. When a new version of a │ │ │ residence module for a special process is loaded, the process must make a fully │ │ │ qualified call to its loop function to switch to the new code. Thus, │ │ │ synchronized code replacement must be used.

    Note

    The name(s) of the user-defined residence module(s) must be listed in the │ │ │ Modules part of the child specification for the special process. Otherwise │ │ │ the release handler cannot find the process.

    Example

    Consider the example ch4 in sys and proc_lib. │ │ │ -When started by a supervisor, the child specification can look as follows:

    {ch4, {ch4, start_link, []},
    │ │ │ - permanent, brutal_kill, worker, [ch4]}

    If ch4 is part of the application sp_app and a new version of the module is │ │ │ +When started by a supervisor, the child specification can look as follows:

    {ch4, {ch4, start_link, []},
    │ │ │ + permanent, brutal_kill, worker, [ch4]}

    If ch4 is part of the application sp_app and a new version of the module is │ │ │ to be loaded when upgrading from version "1" to "2" of this application, │ │ │ -sp_app.appup can look as follows:

    {"2",
    │ │ │ - [{"1", [{update, ch4, {advanced, []}}]}],
    │ │ │ - [{"1", [{update, ch4, {advanced, []}}]}]
    │ │ │ -}.

    The update instruction must contain the tuple {advanced,Extra}. The │ │ │ +sp_app.appup can look as follows:

    {"2",
    │ │ │ + [{"1", [{update, ch4, {advanced, []}}]}],
    │ │ │ + [{"1", [{update, ch4, {advanced, []}}]}]
    │ │ │ +}.

    The update instruction must contain the tuple {advanced,Extra}. The │ │ │ instruction makes the special process call the callback function │ │ │ system_code_change/4, a function the user must implement. The term Extra, in │ │ │ -this case [], is passed as is to system_code_change/4:

    -module(ch4).
    │ │ │ +this case [], is passed as is to system_code_change/4:

    -module(ch4).
    │ │ │  ...
    │ │ │ --export([system_code_change/4]).
    │ │ │ +-export([system_code_change/4]).
    │ │ │  ...
    │ │ │  
    │ │ │ -system_code_change(Chs, _Module, _OldVsn, _Extra) ->
    │ │ │ -    {ok, Chs}.
    • The first argument is the internal state State, passed from │ │ │ +system_code_change(Chs, _Module, _OldVsn, _Extra) -> │ │ │ + {ok, Chs}.

    In this case, all arguments but the first are ignored and the function simply │ │ │ returns the internal state again. This is enough if the code only has been │ │ │ extended. If instead the internal state is changed (similar to the example in │ │ │ @@ -271,85 +271,85 @@ │ │ │ Changing Properties │ │ │ │ │ │

    Since the supervisor is to change its internal state, synchronized code │ │ │ replacement is required. However, a special update instruction must be used.

    First, the new version of the callback module must be loaded, both in the case │ │ │ of upgrade and downgrade. Then the new return value of init/1 can be checked │ │ │ and the internal state be changed accordingly.

    The following upgrade instruction is used for supervisors:

    {update, Module, supervisor}

    Example

    To change the restart strategy of ch_sup (from │ │ │ Supervisor Behaviour) from one_for_one to one_for_all, │ │ │ -change the callback function init/1 in ch_sup.erl:

    -module(ch_sup).
    │ │ │ +change the callback function init/1 in ch_sup.erl:

    -module(ch_sup).
    │ │ │  ...
    │ │ │  
    │ │ │ -init(_Args) ->
    │ │ │ -    {ok, {#{strategy => one_for_all, ...}, ...}}.

    The file ch_app.appup:

    {"2",
    │ │ │ - [{"1", [{update, ch_sup, supervisor}]}],
    │ │ │ - [{"1", [{update, ch_sup, supervisor}]}]
    │ │ │ -}.

    │ │ │ +init(_Args) -> │ │ │ + {ok, {#{strategy => one_for_all, ...}, ...}}.

    The file ch_app.appup:

    {"2",
    │ │ │ + [{"1", [{update, ch_sup, supervisor}]}],
    │ │ │ + [{"1", [{update, ch_sup, supervisor}]}]
    │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ Changing Child Specifications │ │ │

    │ │ │

    The instruction, and thus the .appup file, when changing an existing child │ │ │ -specification, is the same as when changing properties as described earlier:

    {"2",
    │ │ │ - [{"1", [{update, ch_sup, supervisor}]}],
    │ │ │ - [{"1", [{update, ch_sup, supervisor}]}]
    │ │ │ -}.

    The changes do not affect existing child processes. For example, changing the │ │ │ +specification, is the same as when changing properties as described earlier:

    {"2",
    │ │ │ + [{"1", [{update, ch_sup, supervisor}]}],
    │ │ │ + [{"1", [{update, ch_sup, supervisor}]}]
    │ │ │ +}.

    The changes do not affect existing child processes. For example, changing the │ │ │ start function only specifies how the child process is to be restarted, if │ │ │ needed later on.

    The id of the child specification cannot be changed.

    Changing the Modules field of the child specification can affect the release │ │ │ handling process itself, as this field is used to identify which processes are │ │ │ affected when doing a synchronized code replacement.

    │ │ │ │ │ │ │ │ │ │ │ │ Adding and Deleting Child Processes │ │ │

    │ │ │

    As stated earlier, changing child specifications does not affect existing child │ │ │ processes. New child specifications are automatically added, but not deleted. │ │ │ Child processes are not automatically started or terminated, this must be done │ │ │ using apply instructions.

    Example

    Assume a new child process m1 is to be added to ch_sup when │ │ │ upgrading ch_app from "1" to "2". This means m1 is to be deleted when │ │ │ -downgrading from "2" to "1":

    {"2",
    │ │ │ - [{"1",
    │ │ │ -   [{update, ch_sup, supervisor},
    │ │ │ -    {apply, {supervisor, restart_child, [ch_sup, m1]}}
    │ │ │ -   ]}],
    │ │ │ - [{"1",
    │ │ │ -   [{apply, {supervisor, terminate_child, [ch_sup, m1]}},
    │ │ │ -    {apply, {supervisor, delete_child, [ch_sup, m1]}},
    │ │ │ -    {update, ch_sup, supervisor}
    │ │ │ -   ]}]
    │ │ │ -}.

    The order of the instructions is important.

    The supervisor must be registered as ch_sup for the script to work. If the │ │ │ +downgrading from "2" to "1":

    {"2",
    │ │ │ + [{"1",
    │ │ │ +   [{update, ch_sup, supervisor},
    │ │ │ +    {apply, {supervisor, restart_child, [ch_sup, m1]}}
    │ │ │ +   ]}],
    │ │ │ + [{"1",
    │ │ │ +   [{apply, {supervisor, terminate_child, [ch_sup, m1]}},
    │ │ │ +    {apply, {supervisor, delete_child, [ch_sup, m1]}},
    │ │ │ +    {update, ch_sup, supervisor}
    │ │ │ +   ]}]
    │ │ │ +}.

    The order of the instructions is important.

    The supervisor must be registered as ch_sup for the script to work. If the │ │ │ supervisor is not registered, it cannot be accessed directly from the script. │ │ │ Instead a help function that finds the pid of the supervisor and calls │ │ │ supervisor:restart_child, and so on, must be written. This function is then to │ │ │ be called from the script using the apply instruction.

    If the module m1 is introduced in version "2" of ch_app, it must also be │ │ │ -loaded when upgrading and deleted when downgrading:

    {"2",
    │ │ │ - [{"1",
    │ │ │ -   [{add_module, m1},
    │ │ │ -    {update, ch_sup, supervisor},
    │ │ │ -    {apply, {supervisor, restart_child, [ch_sup, m1]}}
    │ │ │ -   ]}],
    │ │ │ - [{"1",
    │ │ │ -   [{apply, {supervisor, terminate_child, [ch_sup, m1]}},
    │ │ │ -    {apply, {supervisor, delete_child, [ch_sup, m1]}},
    │ │ │ -    {update, ch_sup, supervisor},
    │ │ │ -    {delete_module, m1}
    │ │ │ -   ]}]
    │ │ │ -}.

    As stated earlier, the order of the instructions is important. When upgrading, │ │ │ +loaded when upgrading and deleted when downgrading:

    {"2",
    │ │ │ + [{"1",
    │ │ │ +   [{add_module, m1},
    │ │ │ +    {update, ch_sup, supervisor},
    │ │ │ +    {apply, {supervisor, restart_child, [ch_sup, m1]}}
    │ │ │ +   ]}],
    │ │ │ + [{"1",
    │ │ │ +   [{apply, {supervisor, terminate_child, [ch_sup, m1]}},
    │ │ │ +    {apply, {supervisor, delete_child, [ch_sup, m1]}},
    │ │ │ +    {update, ch_sup, supervisor},
    │ │ │ +    {delete_module, m1}
    │ │ │ +   ]}]
    │ │ │ +}.

    As stated earlier, the order of the instructions is important. When upgrading, │ │ │ m1 must be loaded, and the supervisor child specification changed, before the │ │ │ new child process can be started. When downgrading, the child process must be │ │ │ terminated before the child specification is changed and the module is deleted.

    │ │ │ │ │ │ │ │ │ │ │ │ Adding or Deleting a Module │ │ │

    │ │ │ -

    Example

    A new functional module m is added to ch_app:

    {"2",
    │ │ │ - [{"1", [{add_module, m}]}],
    │ │ │ - [{"1", [{delete_module, m}]}]

    │ │ │ +

    Example

    A new functional module m is added to ch_app:

    {"2",
    │ │ │ + [{"1", [{add_module, m}]}],
    │ │ │ + [{"1", [{delete_module, m}]}]

    │ │ │ │ │ │ │ │ │ │ │ │ Starting or Terminating a Process │ │ │

    │ │ │

    In a system structured according to the OTP design principles, any process would │ │ │ be a child process belonging to a supervisor, see │ │ │ @@ -369,29 +369,29 @@ │ │ │ Restarting an Application │ │ │ │ │ │

    Restarting an application is useful when a change is too complicated to be made │ │ │ without restarting the processes, for example, if the supervisor hierarchy has │ │ │ been restructured.

    Example

    When adding a child m1 to ch_sup, as in │ │ │ Adding and Deleting Child Processes in Changing a │ │ │ Supervisor, an alternative to updating the supervisor is to restart the entire │ │ │ -application:

    {"2",
    │ │ │ - [{"1", [{restart_application, ch_app}]}],
    │ │ │ - [{"1", [{restart_application, ch_app}]}]
    │ │ │ -}.

    │ │ │ +application:

    {"2",
    │ │ │ + [{"1", [{restart_application, ch_app}]}],
    │ │ │ + [{"1", [{restart_application, ch_app}]}]
    │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ Changing an Application Specification │ │ │

    │ │ │

    When installing a release, the application specifications are automatically │ │ │ updated before evaluating the relup script. Thus, no instructions are needed │ │ │ -in the .appup file:

    {"2",
    │ │ │ - [{"1", []}],
    │ │ │ - [{"1", []}]
    │ │ │ -}.

    │ │ │ +in the .appup file:

    {"2",
    │ │ │ + [{"1", []}],
    │ │ │ + [{"1", []}]
    │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ Changing Application Configuration │ │ │

    │ │ │

    Changing an application configuration by updating the env key in the .app │ │ │ file is an instance of changing an application specification, see the previous │ │ │ @@ -406,26 +406,26 @@ │ │ │ applications apply to primary applications only. There are no corresponding │ │ │ instructions for included applications. However, since an included application │ │ │ is really a supervision tree with a topmost supervisor, started as a child │ │ │ process to a supervisor in the including application, a .relup file can be │ │ │ manually created.

    Example

    Assume there is a release containing an application prim_app, which │ │ │ have a supervisor prim_sup in its supervision tree.

    In a new version of the release, the application ch_app is to be included in │ │ │ prim_app. That is, its topmost supervisor ch_sup is to be started as a child │ │ │ -process to prim_sup.

    The workflow is as follows:

    Step 1) Edit the code for prim_sup:

    init(...) ->
    │ │ │ -    {ok, {...supervisor flags...,
    │ │ │ -          [...,
    │ │ │ -           {ch_sup, {ch_sup,start_link,[]},
    │ │ │ -            permanent,infinity,supervisor,[ch_sup]},
    │ │ │ -           ...]}}.

    Step 2) Edit the .app file for prim_app:

    {application, prim_app,
    │ │ │ - [...,
    │ │ │ -  {vsn, "2"},
    │ │ │ +process to prim_sup.

    The workflow is as follows:

    Step 1) Edit the code for prim_sup:

    init(...) ->
    │ │ │ +    {ok, {...supervisor flags...,
    │ │ │ +          [...,
    │ │ │ +           {ch_sup, {ch_sup,start_link,[]},
    │ │ │ +            permanent,infinity,supervisor,[ch_sup]},
    │ │ │ +           ...]}}.

    Step 2) Edit the .app file for prim_app:

    {application, prim_app,
    │ │ │ + [...,
    │ │ │ +  {vsn, "2"},
    │ │ │    ...,
    │ │ │ -  {included_applications, [ch_app]},
    │ │ │ +  {included_applications, [ch_app]},
    │ │ │    ...
    │ │ │ - ]}.

    Step 3) Create a new .rel file, including ch_app:

    {release,
    │ │ │ + ]}.

    Step 3) Create a new .rel file, including ch_app:

    {release,
    │ │ │   ...,
    │ │ │   [...,
    │ │ │    {prim_app, "2"},
    │ │ │    {ch_app, "1"}]}.

    The included application can be started in two ways. This is described in the │ │ │ next two sections.

    │ │ │ │ │ │ │ │ │ @@ -480,74 +480,74 @@ │ │ │

    Step 4b) Another way to start the included application (or stop it in the case │ │ │ of downgrade) is by combining instructions for adding and removing child │ │ │ processes to/from prim_sup with instructions for loading/unloading all │ │ │ ch_app code and its application specification.

    Again, the .relup file is created manually, either from scratch or by editing a │ │ │ generated version. Load all code for ch_app first, and also load the │ │ │ application specification, before prim_sup is updated. When downgrading, │ │ │ prim_sup is to updated first, before the code for ch_app and its application │ │ │ -specification are unloaded.

    {"B",
    │ │ │ - [{"A",
    │ │ │ -   [],
    │ │ │ -   [{load_object_code,{ch_app,"1",[ch_sup,ch3]}},
    │ │ │ -    {load_object_code,{prim_app,"2",[prim_sup]}},
    │ │ │ +specification are unloaded.

    {"B",
    │ │ │ + [{"A",
    │ │ │ +   [],
    │ │ │ +   [{load_object_code,{ch_app,"1",[ch_sup,ch3]}},
    │ │ │ +    {load_object_code,{prim_app,"2",[prim_sup]}},
    │ │ │      point_of_no_return,
    │ │ │ -    {load,{ch_sup,brutal_purge,brutal_purge}},
    │ │ │ -    {load,{ch3,brutal_purge,brutal_purge}},
    │ │ │ -    {apply,{application,load,[ch_app]}},
    │ │ │ -    {suspend,[prim_sup]},
    │ │ │ -    {load,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ -    {code_change,up,[{prim_sup,[]}]},
    │ │ │ -    {resume,[prim_sup]},
    │ │ │ -    {apply,{supervisor,restart_child,[prim_sup,ch_sup]}}]}],
    │ │ │ - [{"A",
    │ │ │ -   [],
    │ │ │ -   [{load_object_code,{prim_app,"1",[prim_sup]}},
    │ │ │ +    {load,{ch_sup,brutal_purge,brutal_purge}},
    │ │ │ +    {load,{ch3,brutal_purge,brutal_purge}},
    │ │ │ +    {apply,{application,load,[ch_app]}},
    │ │ │ +    {suspend,[prim_sup]},
    │ │ │ +    {load,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ +    {code_change,up,[{prim_sup,[]}]},
    │ │ │ +    {resume,[prim_sup]},
    │ │ │ +    {apply,{supervisor,restart_child,[prim_sup,ch_sup]}}]}],
    │ │ │ + [{"A",
    │ │ │ +   [],
    │ │ │ +   [{load_object_code,{prim_app,"1",[prim_sup]}},
    │ │ │      point_of_no_return,
    │ │ │ -    {apply,{supervisor,terminate_child,[prim_sup,ch_sup]}},
    │ │ │ -    {apply,{supervisor,delete_child,[prim_sup,ch_sup]}},
    │ │ │ -    {suspend,[prim_sup]},
    │ │ │ -    {load,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ -    {code_change,down,[{prim_sup,[]}]},
    │ │ │ -    {resume,[prim_sup]},
    │ │ │ -    {remove,{ch_sup,brutal_purge,brutal_purge}},
    │ │ │ -    {remove,{ch3,brutal_purge,brutal_purge}},
    │ │ │ -    {purge,[ch_sup,ch3]},
    │ │ │ -    {apply,{application,unload,[ch_app]}}]}]
    │ │ │ -}.

    │ │ │ + {apply,{supervisor,terminate_child,[prim_sup,ch_sup]}}, │ │ │ + {apply,{supervisor,delete_child,[prim_sup,ch_sup]}}, │ │ │ + {suspend,[prim_sup]}, │ │ │ + {load,{prim_sup,brutal_purge,brutal_purge}}, │ │ │ + {code_change,down,[{prim_sup,[]}]}, │ │ │ + {resume,[prim_sup]}, │ │ │ + {remove,{ch_sup,brutal_purge,brutal_purge}}, │ │ │ + {remove,{ch3,brutal_purge,brutal_purge}}, │ │ │ + {purge,[ch_sup,ch3]}, │ │ │ + {apply,{application,unload,[ch_app]}}]}] │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ Changing Non-Erlang Code │ │ │

    │ │ │

    Changing code for a program written in another programming language than Erlang, │ │ │ for example, a port program, is application-dependent and OTP provides no │ │ │ special support.

    Example

    When changing code for a port program, assume that the Erlang process │ │ │ controlling the port is a gen_server portc and that the port is opened in │ │ │ -the callback function init/1:

    init(...) ->
    │ │ │ +the callback function init/1:

    init(...) ->
    │ │ │      ...,
    │ │ │ -    PortPrg = filename:join(code:priv_dir(App), "portc"),
    │ │ │ -    Port = open_port({spawn,PortPrg}, [...]),
    │ │ │ +    PortPrg = filename:join(code:priv_dir(App), "portc"),
    │ │ │ +    Port = open_port({spawn,PortPrg}, [...]),
    │ │ │      ...,
    │ │ │ -    {ok, #state{port=Port, ...}}.

    If the port program is to be updated, the code for the gen_server can be │ │ │ + {ok, #state{port=Port, ...}}.

    If the port program is to be updated, the code for the gen_server can be │ │ │ extended with a code_change/3 function, which closes the old port and opens a │ │ │ new port. (If necessary, the gen_server can first request data that must be │ │ │ -saved from the port program and pass this data to the new port):

    code_change(_OldVsn, State, port) ->
    │ │ │ +saved from the port program and pass this data to the new port):

    code_change(_OldVsn, State, port) ->
    │ │ │      State#state.port ! close,
    │ │ │      receive
    │ │ │ -        {Port,close} ->
    │ │ │ +        {Port,close} ->
    │ │ │              true
    │ │ │      end,
    │ │ │ -    PortPrg = filename:join(code:priv_dir(App), "portc"),
    │ │ │ -    Port = open_port({spawn,PortPrg}, [...]),
    │ │ │ -    {ok, #state{port=Port, ...}}.

    Update the application version number in the .app file and write an .appup │ │ │ -file:

    ["2",
    │ │ │ - [{"1", [{update, portc, {advanced,port}}]}],
    │ │ │ - [{"1", [{update, portc, {advanced,port}}]}]
    │ │ │ -].

    Ensure that the priv directory, where the C program is located, is included in │ │ │ -the new release package:

    1> systools:make_tar("my_release", [{dirs,[priv]}]).
    │ │ │ +    PortPrg = filename:join(code:priv_dir(App), "portc"),
    │ │ │ +    Port = open_port({spawn,PortPrg}, [...]),
    │ │ │ +    {ok, #state{port=Port, ...}}.

    Update the application version number in the .app file and write an .appup │ │ │ +file:

    ["2",
    │ │ │ + [{"1", [{update, portc, {advanced,port}}]}],
    │ │ │ + [{"1", [{update, portc, {advanced,port}}]}]
    │ │ │ +].

    Ensure that the priv directory, where the C program is located, is included in │ │ │ +the new release package:

    1> systools:make_tar("my_release", [{dirs,[priv]}]).
    │ │ │  ...

    │ │ │ │ │ │ │ │ │ │ │ │ Runtime System Restart and Upgrade │ │ │

    │ │ │

    Two upgrade instructions restart the runtime system:

    • restart_new_emulator

      Intended when ERTS, Kernel, STDLIB, or SASL is upgraded. It is automatically │ │ │ @@ -555,22 +555,22 @@ │ │ │ executed before all other upgrade instructions. For more information about │ │ │ this instruction, see restart_new_emulator (Low-Level) in │ │ │ Release Handling Instructions.

    • restart_emulator

      Used when a restart of the runtime system is required after all other upgrade │ │ │ instructions are executed. For more information about this instruction, see │ │ │ restart_emulator (Low-Level) in │ │ │ Release Handling Instructions.

    If a runtime system restart is necessary and no upgrade instructions are needed, │ │ │ that is, if the restart itself is enough for the upgraded applications to start │ │ │ -running the new versions, a simple .relup file can be created manually:

    {"B",
    │ │ │ - [{"A",
    │ │ │ -   [],
    │ │ │ -   [restart_emulator]}],
    │ │ │ - [{"A",
    │ │ │ -   [],
    │ │ │ -   [restart_emulator]}]
    │ │ │ -}.

    In this case, the release handler framework with automatic packing and unpacking │ │ │ +running the new versions, a simple .relup file can be created manually:

    {"B",
    │ │ │ + [{"A",
    │ │ │ +   [],
    │ │ │ +   [restart_emulator]}],
    │ │ │ + [{"A",
    │ │ │ +   [],
    │ │ │ +   [restart_emulator]}]
    │ │ │ +}.

    In this case, the release handler framework with automatic packing and unpacking │ │ │ of release packages, automatic path updates, and so on, can be used without │ │ │ having to specify .appup files.

    │ │ │ │ │ │ │ │ │
    │ │ │
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/benchmarking.html │ │ │ @@ -144,16 +144,16 @@ │ │ │ fast as possible, what can we do? One way could be to generate more │ │ │ than two bytes at the time.

    % erlperf 'rand:bytes(100).' 'crypto:strong_rand_bytes(100).'
    │ │ │  Code                                   ||        QPS       Time   Rel
    │ │ │  rand:bytes(100).                        1    2124 Ki     470 ns  100%
    │ │ │  crypto:strong_rand_bytes(100).          1    1915 Ki     522 ns   90%

    rand:bytes/1 is still faster when we generate 100 bytes at the time, │ │ │ but the relative difference is smaller.

    % erlperf 'rand:bytes(1000).' 'crypto:strong_rand_bytes(1000).'
    │ │ │  Code                                    ||        QPS       Time   Rel
    │ │ │ -crypto:strong_rand_bytes(1000).          1    1518 Ki     658 ns  100%
    │ │ │ -rand:bytes(1000).                        1     284 Ki    3521 ns   19%

    When we generate 1000 bytes at the time, crypto:strong_rand_bytes/1 is │ │ │ +crypto:strong_rand_bytes(1000). 1 1518 Ki 658 ns 100% │ │ │ +rand:bytes(1000). 1 284 Ki 3521 ns 19%

    When we generate 1000 bytes at the time, crypto:strong_rand_bytes/1 is │ │ │ now the fastest.

    │ │ │ │ │ │ │ │ │ │ │ │ Benchmarking using Erlang/OTP functionality │ │ │

    │ │ │

    Benchmarks can measure wall-clock time or CPU time.

    • timer:tc/3 measures wall-clock time. The advantage with wall-clock time is │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/binaryhandling.html │ │ │ @@ -114,43 +114,43 @@ │ │ │ │ │ │ Constructing and Matching Binaries │ │ │ │ │ │ │ │ │

      This section gives a few examples on how to handle binaries in an efficient way. │ │ │ The sections that follow take an in-depth look at how binaries are implemented │ │ │ and how to best take advantages of the optimizations done by the compiler and │ │ │ -runtime system.

      Binaries can be efficiently built in the following way:

      DO

      my_list_to_binary(List) ->
      │ │ │ -    my_list_to_binary(List, <<>>).
      │ │ │ +runtime system.

      Binaries can be efficiently built in the following way:

      DO

      my_list_to_binary(List) ->
      │ │ │ +    my_list_to_binary(List, <<>>).
      │ │ │  
      │ │ │ -my_list_to_binary([H|T], Acc) ->
      │ │ │ -    my_list_to_binary(T, <<Acc/binary,H>>);
      │ │ │ -my_list_to_binary([], Acc) ->
      │ │ │ +my_list_to_binary([H|T], Acc) ->
      │ │ │ +    my_list_to_binary(T, <<Acc/binary,H>>);
      │ │ │ +my_list_to_binary([], Acc) ->
      │ │ │      Acc.

      Appending data to a binary as in the example is efficient because it is │ │ │ specially optimized by the runtime system to avoid copying the Acc binary │ │ │ -every time.

      Prepending data to a binary in a loop is not efficient:

      DO NOT

      rev_list_to_binary(List) ->
      │ │ │ -    rev_list_to_binary(List, <<>>).
      │ │ │ +every time.

      Prepending data to a binary in a loop is not efficient:

      DO NOT

      rev_list_to_binary(List) ->
      │ │ │ +    rev_list_to_binary(List, <<>>).
      │ │ │  
      │ │ │ -rev_list_to_binary([H|T], Acc) ->
      │ │ │ -    rev_list_to_binary(T, <<H,Acc/binary>>);
      │ │ │ -rev_list_to_binary([], Acc) ->
      │ │ │ +rev_list_to_binary([H|T], Acc) ->
      │ │ │ +    rev_list_to_binary(T, <<H,Acc/binary>>);
      │ │ │ +rev_list_to_binary([], Acc) ->
      │ │ │      Acc.

      This is not efficient for long lists because the Acc binary is copied every │ │ │ -time. One way to make the function more efficient is like this:

      DO NOT

      rev_list_to_binary(List) ->
      │ │ │ -    rev_list_to_binary(lists:reverse(List), <<>>).
      │ │ │ +time. One way to make the function more efficient is like this:

      DO NOT

      rev_list_to_binary(List) ->
      │ │ │ +    rev_list_to_binary(lists:reverse(List), <<>>).
      │ │ │  
      │ │ │ -rev_list_to_binary([H|T], Acc) ->
      │ │ │ -    rev_list_to_binary(T, <<Acc/binary,H>>);
      │ │ │ -rev_list_to_binary([], Acc) ->
      │ │ │ -    Acc.

      Another way to avoid copying the binary each time is like this:

      DO

      rev_list_to_binary([H|T]) ->
      │ │ │ -    RevTail = rev_list_to_binary(T),
      │ │ │ -    <<RevTail/binary,H>>;
      │ │ │ -rev_list_to_binary([]) ->
      │ │ │ -    <<>>.

      Note that in each of the DO examples, the binary to be appended to is always │ │ │ -given as the first segment.

      Binaries can be efficiently matched in the following way:

      DO

      my_binary_to_list(<<H,T/binary>>) ->
      │ │ │ -    [H|my_binary_to_list(T)];
      │ │ │ -my_binary_to_list(<<>>) -> [].

      │ │ │ +rev_list_to_binary([H|T], Acc) -> │ │ │ + rev_list_to_binary(T, <<Acc/binary,H>>); │ │ │ +rev_list_to_binary([], Acc) -> │ │ │ + Acc.

      Another way to avoid copying the binary each time is like this:

      DO

      rev_list_to_binary([H|T]) ->
      │ │ │ +    RevTail = rev_list_to_binary(T),
      │ │ │ +    <<RevTail/binary,H>>;
      │ │ │ +rev_list_to_binary([]) ->
      │ │ │ +    <<>>.

      Note that in each of the DO examples, the binary to be appended to is always │ │ │ +given as the first segment.

      Binaries can be efficiently matched in the following way:

      DO

      my_binary_to_list(<<H,T/binary>>) ->
      │ │ │ +    [H|my_binary_to_list(T)];
      │ │ │ +my_binary_to_list(<<>>) -> [].

      │ │ │ │ │ │ │ │ │ │ │ │ How Binaries are Implemented │ │ │

      │ │ │

      Internally, binaries and bitstrings are implemented in the same way. In this │ │ │ section, they are called binaries because that is what they are called in the │ │ │ @@ -205,29 +205,29 @@ │ │ │ called referential transparency) of Erlang would break.

      │ │ │ │ │ │ │ │ │ │ │ │ Constructing Binaries │ │ │

      │ │ │

      Appending to a binary or bitstring in the following way is specially optimized │ │ │ -to avoid copying the binary:

      <<Binary/binary, ...>>
      │ │ │ +to avoid copying the binary:

      <<Binary/binary, ...>>
      │ │ │  %% - OR -
      │ │ │ -<<Binary/bitstring, ...>>

      This optimization is applied by the runtime system in a way that makes it │ │ │ +<<Binary/bitstring, ...>>

      This optimization is applied by the runtime system in a way that makes it │ │ │ effective in most circumstances (for exceptions, see │ │ │ Circumstances That Force Copying). The │ │ │ optimization in its basic form does not need any help from the compiler. │ │ │ However, the compiler add hints to the runtime system when it is safe to apply │ │ │ the optimization in a more efficient way.

      Change

      The compiler support for making the optimization more efficient was added in │ │ │ Erlang/OTP 26.

      To explain how the basic optimization works, let us examine the following code │ │ │ -line by line:

      Bin0 = <<0>>,                    %% 1
      │ │ │ -Bin1 = <<Bin0/binary,1,2,3>>,    %% 2
      │ │ │ -Bin2 = <<Bin1/binary,4,5,6>>,    %% 3
      │ │ │ -Bin3 = <<Bin2/binary,7,8,9>>,    %% 4
      │ │ │ -Bin4 = <<Bin1/binary,17>>,       %% 5 !!!
      │ │ │ -{Bin4,Bin3}                      %% 6
      • Line 1 (marked with the %% 1 comment), assigns a │ │ │ +line by line:

        Bin0 = <<0>>,                    %% 1
        │ │ │ +Bin1 = <<Bin0/binary,1,2,3>>,    %% 2
        │ │ │ +Bin2 = <<Bin1/binary,4,5,6>>,    %% 3
        │ │ │ +Bin3 = <<Bin2/binary,7,8,9>>,    %% 4
        │ │ │ +Bin4 = <<Bin1/binary,17>>,       %% 5 !!!
        │ │ │ +{Bin4,Bin3}                      %% 6
        • Line 1 (marked with the %% 1 comment), assigns a │ │ │ heap binary to the Bin0 variable.

        • Line 2 is an append operation. As Bin0 has not been involved in an append │ │ │ operation, a new refc binary is created and │ │ │ the contents of Bin0 is copied into it. The ProcBin part of the refc │ │ │ binary has its size set to the size of the data stored in the binary, while │ │ │ the binary object has extra space allocated. The size of the binary object is │ │ │ either twice the size of Bin1 or 256, whichever is larger. In this case it │ │ │ is 256.

        • Line 3 is more interesting. Bin1 has been used in an append operation, and │ │ │ @@ -253,23 +253,23 @@ │ │ │ handle an append operation to a heap binary by copying it to a refc binary (line │ │ │ 2), and also handle an append operation to a previous version of the binary by │ │ │ copying it (line 5). The support for doing that does not come for free. For │ │ │ example, to make it possible to know when it is necessary to copy the binary, │ │ │ for every append operation, the runtime system must create a sub binary.

          When the compiler can determine that none of those situations need to be handled │ │ │ and that the append operation cannot possibly fail, the compiler generates code │ │ │ that causes the runtime system to apply a more efficient variant of the │ │ │ -optimization.

          Example:

          -module(repack).
          │ │ │ --export([repack/1]).
          │ │ │ +optimization.

          Example:

          -module(repack).
          │ │ │ +-export([repack/1]).
          │ │ │  
          │ │ │ -repack(Bin) when is_binary(Bin) ->
          │ │ │ -    repack(Bin, <<>>).
          │ │ │ +repack(Bin) when is_binary(Bin) ->
          │ │ │ +    repack(Bin, <<>>).
          │ │ │  
          │ │ │ -repack(<<C:8,T/binary>>, Result) ->
          │ │ │ -    repack(T, <<Result/binary,C:16>>);
          │ │ │ -repack(<<>>, Result) ->
          │ │ │ +repack(<<C:8,T/binary>>, Result) ->
          │ │ │ +    repack(T, <<Result/binary,C:16>>);
          │ │ │ +repack(<<>>, Result) ->
          │ │ │      Result.

          The repack/2 function only keeps a single version of the binary, so there is │ │ │ never any need to copy the binary. The compiler rewrites the creation of the │ │ │ empty binary in repack/1 to instead create a refc binary with 256 bytes │ │ │ already reserved; thus, the append operation in repack/2 never needs to handle │ │ │ a binary not prepared for appending.

          │ │ │ │ │ │ │ │ │ @@ -281,72 +281,72 @@ │ │ │ reason is that the binary object can be moved (reallocated) during an append │ │ │ operation, and when that happens, the pointer in the ProcBin must be updated. If │ │ │ there would be more than one ProcBin pointing to the binary object, it would not │ │ │ be possible to find and update all of them.

          Therefore, certain operations on a binary mark it so that any future append │ │ │ operation will be forced to copy the binary. In most cases, the binary object │ │ │ will be shrunk at the same time to reclaim the extra space allocated for │ │ │ growing.

          When appending to a binary as follows, only the binary returned from the latest │ │ │ -append operation will support further cheap append operations:

          Bin = <<Bin0,...>>

          In the code fragment in the beginning of this section, appending to Bin will │ │ │ +append operation will support further cheap append operations:

          Bin = <<Bin0,...>>

          In the code fragment in the beginning of this section, appending to Bin will │ │ │ be cheap, while appending to Bin0 will force the creation of a new binary and │ │ │ copying of the contents of Bin0.

          If a binary is sent as a message to a process or port, the binary will be shrunk │ │ │ and any further append operation will copy the binary data into a new binary. │ │ │ For example, in the following code fragment Bin1 will be copied in the third │ │ │ -line:

          Bin1 = <<Bin0,...>>,
          │ │ │ +line:

          Bin1 = <<Bin0,...>>,
          │ │ │  PortOrPid ! Bin1,
          │ │ │ -Bin = <<Bin1,...>>  %% Bin1 will be COPIED

          The same happens if you insert a binary into an Ets table, send it to a port │ │ │ +Bin = <<Bin1,...>> %% Bin1 will be COPIED

          The same happens if you insert a binary into an Ets table, send it to a port │ │ │ using erlang:port_command/2, or pass it to │ │ │ enif_inspect_binary in a NIF.

          Matching a binary will also cause it to shrink and the next append operation │ │ │ -will copy the binary data:

          Bin1 = <<Bin0,...>>,
          │ │ │ -<<X,Y,Z,T/binary>> = Bin1,
          │ │ │ -Bin = <<Bin1,...>>  %% Bin1 will be COPIED

          The reason is that a match context contains a │ │ │ +will copy the binary data:

          Bin1 = <<Bin0,...>>,
          │ │ │ +<<X,Y,Z,T/binary>> = Bin1,
          │ │ │ +Bin = <<Bin1,...>>  %% Bin1 will be COPIED

          The reason is that a match context contains a │ │ │ direct pointer to the binary data.

          If a process simply keeps binaries (either in "loop data" or in the process │ │ │ dictionary), the garbage collector can eventually shrink the binaries. If only │ │ │ one such binary is kept, it will not be shrunk. If the process later appends to │ │ │ a binary that has been shrunk, the binary object will be reallocated to make │ │ │ place for the data to be appended.

          │ │ │ │ │ │ │ │ │ │ │ │ Matching Binaries │ │ │

          │ │ │ -

          Let us revisit the example in the beginning of the previous section:

          DO

          my_binary_to_list(<<H,T/binary>>) ->
          │ │ │ -    [H|my_binary_to_list(T)];
          │ │ │ -my_binary_to_list(<<>>) -> [].

          The first time my_binary_to_list/1 is called, a │ │ │ +

          Let us revisit the example in the beginning of the previous section:

          DO

          my_binary_to_list(<<H,T/binary>>) ->
          │ │ │ +    [H|my_binary_to_list(T)];
          │ │ │ +my_binary_to_list(<<>>) -> [].

          The first time my_binary_to_list/1 is called, a │ │ │ match context is created. The match context │ │ │ points to the first byte of the binary. 1 byte is matched out and the match │ │ │ context is updated to point to the second byte in the binary.

          At this point it would make sense to create a │ │ │ sub binary, but in this particular example the │ │ │ compiler sees that there will soon be a call to a function (in this case, to │ │ │ my_binary_to_list/1 itself) that immediately will create a new match context │ │ │ and discard the sub binary.

          Therefore my_binary_to_list/1 calls itself with the match context instead of │ │ │ with a sub binary. The instruction that initializes the matching operation │ │ │ basically does nothing when it sees that it was passed a match context instead │ │ │ of a binary.

          When the end of the binary is reached and the second clause matches, the match │ │ │ context will simply be discarded (removed in the next garbage collection, as │ │ │ there is no longer any reference to it).

          To summarize, my_binary_to_list/1 only needs to create one match context and │ │ │ no sub binaries.

          Notice that the match context in my_binary_to_list/1 was discarded when the │ │ │ entire binary had been traversed. What happens if the iteration stops before it │ │ │ -has reached the end of the binary? Will the optimization still work?

          after_zero(<<0,T/binary>>) ->
          │ │ │ +has reached the end of the binary? Will the optimization still work?

          after_zero(<<0,T/binary>>) ->
          │ │ │      T;
          │ │ │ -after_zero(<<_,T/binary>>) ->
          │ │ │ -    after_zero(T);
          │ │ │ -after_zero(<<>>) ->
          │ │ │ -    <<>>.

          Yes, it will. The compiler will remove the building of the sub binary in the │ │ │ +after_zero(<<_,T/binary>>) -> │ │ │ + after_zero(T); │ │ │ +after_zero(<<>>) -> │ │ │ + <<>>.

          Yes, it will. The compiler will remove the building of the sub binary in the │ │ │ second clause:

          ...
          │ │ │ -after_zero(<<_,T/binary>>) ->
          │ │ │ -    after_zero(T);
          │ │ │ -...

          But it will generate code that builds a sub binary in the first clause:

          after_zero(<<0,T/binary>>) ->
          │ │ │ +after_zero(<<_,T/binary>>) ->
          │ │ │ +    after_zero(T);
          │ │ │ +...

          But it will generate code that builds a sub binary in the first clause:

          after_zero(<<0,T/binary>>) ->
          │ │ │      T;
          │ │ │  ...

          Therefore, after_zero/1 builds one match context and one sub binary (assuming │ │ │ -it is passed a binary that contains a zero byte).

          Code like the following will also be optimized:

          all_but_zeroes_to_list(Buffer, Acc, 0) ->
          │ │ │ -    {lists:reverse(Acc),Buffer};
          │ │ │ -all_but_zeroes_to_list(<<0,T/binary>>, Acc, Remaining) ->
          │ │ │ -    all_but_zeroes_to_list(T, Acc, Remaining-1);
          │ │ │ -all_but_zeroes_to_list(<<Byte,T/binary>>, Acc, Remaining) ->
          │ │ │ -    all_but_zeroes_to_list(T, [Byte|Acc], Remaining-1).

          The compiler removes building of sub binaries in the second and third clauses, │ │ │ +it is passed a binary that contains a zero byte).

          Code like the following will also be optimized:

          all_but_zeroes_to_list(Buffer, Acc, 0) ->
          │ │ │ +    {lists:reverse(Acc),Buffer};
          │ │ │ +all_but_zeroes_to_list(<<0,T/binary>>, Acc, Remaining) ->
          │ │ │ +    all_but_zeroes_to_list(T, Acc, Remaining-1);
          │ │ │ +all_but_zeroes_to_list(<<Byte,T/binary>>, Acc, Remaining) ->
          │ │ │ +    all_but_zeroes_to_list(T, [Byte|Acc], Remaining-1).

          The compiler removes building of sub binaries in the second and third clauses, │ │ │ and it adds an instruction to the first clause that converts Buffer from a │ │ │ match context to a sub binary (or do nothing if Buffer is a binary already).

          But in more complicated code, how can one know whether the optimization is │ │ │ applied or not?

          │ │ │ │ │ │ │ │ │ │ │ │ Option bin_opt_info │ │ │ @@ -354,38 +354,38 @@ │ │ │

          Use the bin_opt_info option to have the compiler print a lot of information │ │ │ about binary optimizations. It can be given either to the compiler or erlc:

          erlc +bin_opt_info Mod.erl

          or passed through an environment variable:

          export ERL_COMPILER_OPTIONS=bin_opt_info

          Notice that the bin_opt_info is not meant to be a permanent option added to │ │ │ your Makefiles, because all messages that it generates cannot be eliminated. │ │ │ Therefore, passing the option through the environment is in most cases the most │ │ │ practical approach.

          The warnings look as follows:

          ./efficiency_guide.erl:60: Warning: NOT OPTIMIZED: binary is returned from the function
          │ │ │  ./efficiency_guide.erl:62: Warning: OPTIMIZED: match context reused

          To make it clearer exactly what code the warnings refer to, the warnings in the │ │ │ following examples are inserted as comments after the clause they refer to, for │ │ │ -example:

          after_zero(<<0,T/binary>>) ->
          │ │ │ +example:

          after_zero(<<0,T/binary>>) ->
          │ │ │           %% BINARY CREATED: binary is returned from the function
          │ │ │      T;
          │ │ │ -after_zero(<<_,T/binary>>) ->
          │ │ │ +after_zero(<<_,T/binary>>) ->
          │ │ │           %% OPTIMIZED: match context reused
          │ │ │ -    after_zero(T);
          │ │ │ -after_zero(<<>>) ->
          │ │ │ -    <<>>.

          The warning for the first clause says that the creation of a sub binary cannot │ │ │ + after_zero(T); │ │ │ +after_zero(<<>>) -> │ │ │ + <<>>.

          The warning for the first clause says that the creation of a sub binary cannot │ │ │ be delayed, because it will be returned. The warning for the second clause says │ │ │ that a sub binary will not be created (yet).

          │ │ │ │ │ │ │ │ │ │ │ │ Unused Variables │ │ │

          │ │ │

          The compiler figures out if a variable is unused. The same code is generated for │ │ │ -each of the following functions:

          count1(<<_,T/binary>>, Count) -> count1(T, Count+1);
          │ │ │ -count1(<<>>, Count) -> Count.
          │ │ │ +each of the following functions:

          count1(<<_,T/binary>>, Count) -> count1(T, Count+1);
          │ │ │ +count1(<<>>, Count) -> Count.
          │ │ │  
          │ │ │ -count2(<<H,T/binary>>, Count) -> count2(T, Count+1);
          │ │ │ -count2(<<>>, Count) -> Count.
          │ │ │ +count2(<<H,T/binary>>, Count) -> count2(T, Count+1);
          │ │ │ +count2(<<>>, Count) -> Count.
          │ │ │  
          │ │ │ -count3(<<_H,T/binary>>, Count) -> count3(T, Count+1);
          │ │ │ -count3(<<>>, Count) -> Count.

          In each iteration, the first 8 bits in the binary will be skipped, not matched │ │ │ +count3(<<_H,T/binary>>, Count) -> count3(T, Count+1); │ │ │ +count3(<<>>, Count) -> Count.

          In each iteration, the first 8 bits in the binary will be skipped, not matched │ │ │ out.

          │ │ │ │ │ │ │ │ │
          │ │ │
          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Introduction │ │ │ │ │ │

          The complete specification for the bit syntax appears in the │ │ │ Reference Manual.

          In Erlang, a Bin is used for constructing binaries and matching binary patterns. │ │ │ -A Bin is written with the following syntax:

          <<E1, E2, ... En>>

          A Bin is a low-level sequence of bits or bytes. The purpose of a Bin is to │ │ │ -enable construction of binaries:

          Bin = <<E1, E2, ... En>>

          All elements must be bound. Or match a binary:

          <<E1, E2, ... En>> = Bin

          Here, Bin is bound and the elements are bound or unbound, as in any match.

          A Bin does not need to consist of a whole number of bytes.

          A bitstring is a sequence of zero or more bits, where the number of bits does │ │ │ +A Bin is written with the following syntax:

          <<E1, E2, ... En>>

          A Bin is a low-level sequence of bits or bytes. The purpose of a Bin is to │ │ │ +enable construction of binaries:

          Bin = <<E1, E2, ... En>>

          All elements must be bound. Or match a binary:

          <<E1, E2, ... En>> = Bin

          Here, Bin is bound and the elements are bound or unbound, as in any match.

          A Bin does not need to consist of a whole number of bytes.

          A bitstring is a sequence of zero or more bits, where the number of bits does │ │ │ not need to be divisible by 8. If the number of bits is divisible by 8, the │ │ │ bitstring is also a binary.

          Each element specifies a certain segment of the bitstring. A segment is a set │ │ │ of contiguous bits of the binary (not necessarily on a byte boundary). The first │ │ │ element specifies the initial segment, the second element specifies the │ │ │ following segment, and so on.

          The following examples illustrate how binaries are constructed, or matched, and │ │ │ how elements and tails are specified.

          │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

          │ │ │

          Example 1: A binary can be constructed from a set of constants or a string │ │ │ -literal:

          Bin11 = <<1, 17, 42>>,
          │ │ │ -Bin12 = <<"abc">>

          This gives two binaries of size 3, with the following evaluations:

          Example 2:Similarly, a binary can be constructed from a set of bound │ │ │ +literal:

          Bin11 = <<1, 17, 42>>,
          │ │ │ +Bin12 = <<"abc">>

          This gives two binaries of size 3, with the following evaluations:

          Example 2:Similarly, a binary can be constructed from a set of bound │ │ │ variables:

          A = 1, B = 17, C = 42,
          │ │ │ -Bin2 = <<A, B, C:16>>

          This gives a binary of size 4. Here, a size expression is used for the │ │ │ +Bin2 = <<A, B, C:16>>

          This gives a binary of size 4. Here, a size expression is used for the │ │ │ variable C to specify a 16-bits segment of Bin2.

          binary_to_list(Bin2) evaluates to [1, 17, 00, 42].

          Example 3: A Bin can also be used for matching. D, E, and F are unbound │ │ │ -variables, and Bin2 is bound, as in Example 2:

          <<D:16, E, F/binary>> = Bin2

          This gives D = 273, E = 00, and F binds to a binary of size 1: │ │ │ +variables, and Bin2 is bound, as in Example 2:

          <<D:16, E, F/binary>> = Bin2

          This gives D = 273, E = 00, and F binds to a binary of size 1: │ │ │ binary_to_list(F) = [42].

          Example 4: The following is a more elaborate example of matching. Here, │ │ │ Dgram is bound to the consecutive bytes of an IP datagram of IP protocol │ │ │ -version 4. The ambition is to extract the header and the data of the datagram:

          -define(IP_VERSION, 4).
          │ │ │ --define(IP_MIN_HDR_LEN, 5).
          │ │ │ +version 4. The ambition is to extract the header and the data of the datagram:

          -define(IP_VERSION, 4).
          │ │ │ +-define(IP_MIN_HDR_LEN, 5).
          │ │ │  
          │ │ │ -DgramSize = byte_size(Dgram),
          │ │ │ +DgramSize = byte_size(Dgram),
          │ │ │  case Dgram of
          │ │ │ -    <<?IP_VERSION:4, HLen:4, SrvcType:8, TotLen:16,
          │ │ │ +    <<?IP_VERSION:4, HLen:4, SrvcType:8, TotLen:16,
          │ │ │        ID:16, Flgs:3, FragOff:13,
          │ │ │        TTL:8, Proto:8, HdrChkSum:16,
          │ │ │        SrcIP:32,
          │ │ │ -      DestIP:32, RestDgram/binary>> when HLen>=5, 4*HLen=<DgramSize ->
          │ │ │ -        OptsLen = 4*(HLen - ?IP_MIN_HDR_LEN),
          │ │ │ -        <<Opts:OptsLen/binary,Data/binary>> = RestDgram,
          │ │ │ +      DestIP:32, RestDgram/binary>> when HLen>=5, 4*HLen=<DgramSize ->
          │ │ │ +        OptsLen = 4*(HLen - ?IP_MIN_HDR_LEN),
          │ │ │ +        <<Opts:OptsLen/binary,Data/binary>> = RestDgram,
          │ │ │      ...
          │ │ │  end.

          Here, the segment corresponding to the Opts variable has a type modifier, │ │ │ specifying that Opts is to bind to a binary. All other variables have the │ │ │ default type equal to unsigned integer.

          An IP datagram header is of variable length. This length is measured in the │ │ │ number of 32-bit words and is given in the segment corresponding to HLen. The │ │ │ minimum value of HLen is 5. It is the segment corresponding to Opts that is │ │ │ variable, so if HLen is equal to 5, Opts becomes an empty binary.

          The tail variables RestDgram and Data bind to binaries, as all tail │ │ │ @@ -218,80 +218,80 @@ │ │ │

          This section describes the rules for constructing binaries using the bit syntax. │ │ │ Unlike when constructing lists or tuples, the construction of a binary can fail │ │ │ with a badarg exception.

          There can be zero or more segments in a binary to be constructed. The expression │ │ │ <<>> constructs a zero length binary.

          Each segment in a binary can consist of zero or more bits. There are no │ │ │ alignment rules for individual segments of type integer and float. For │ │ │ binaries and bitstrings without size, the unit specifies the alignment. Since │ │ │ the default alignment for the binary type is 8, the size of a binary segment │ │ │ -must be a multiple of 8 bits, that is, only whole bytes.

          Example:

          <<Bin/binary,Bitstring/bitstring>>

          The variable Bin must contain a whole number of bytes, because the binary │ │ │ +must be a multiple of 8 bits, that is, only whole bytes.

          Example:

          <<Bin/binary,Bitstring/bitstring>>

          The variable Bin must contain a whole number of bytes, because the binary │ │ │ type defaults to unit:8. A badarg exception is generated if Bin consist │ │ │ of, for example, 17 bits.

          The Bitstring variable can consist of any number of bits, for example, 0, 1, │ │ │ 8, 11, 17, 42, and so on. This is because the default unit for bitstrings │ │ │ is 1.

          For clarity, it is recommended not to change the unit size for binaries. │ │ │ Instead, use binary when you need byte alignment and bitstring when you need │ │ │ bit alignment.

          The following example successfully constructs a bitstring of 7 bits, provided │ │ │ -that all of X and Y are integers:

          <<X:1,Y:6>>

          As mentioned earlier, segments have the following general syntax:

          Value:Size/TypeSpecifierList

          When constructing binaries, Value and Size can be any Erlang expression. │ │ │ +that all of X and Y are integers:

          <<X:1,Y:6>>

          As mentioned earlier, segments have the following general syntax:

          Value:Size/TypeSpecifierList

          When constructing binaries, Value and Size can be any Erlang expression. │ │ │ However, for syntactical reasons, both Value and Size must be enclosed in │ │ │ parenthesis if the expression consists of anything more than a single literal or │ │ │ -a variable. The following gives a compiler syntax error:

          <<X+1:8>>

          This expression must be rewritten into the following, to be accepted by the │ │ │ -compiler:

          <<(X+1):8>>

          │ │ │ +a variable. The following gives a compiler syntax error:

          <<X+1:8>>

          This expression must be rewritten into the following, to be accepted by the │ │ │ +compiler:

          <<(X+1):8>>

          │ │ │ │ │ │ │ │ │ │ │ │ Including Literal Strings │ │ │

          │ │ │ -

          A literal string can be written instead of an element:

          <<"hello">>

          This is syntactic sugar for the following:

          <<$h,$e,$l,$l,$o>>

          │ │ │ +

          A literal string can be written instead of an element:

          <<"hello">>

          This is syntactic sugar for the following:

          <<$h,$e,$l,$l,$o>>

          │ │ │ │ │ │ │ │ │ │ │ │ Matching Binaries │ │ │

          │ │ │

          This section describes the rules for matching binaries, using the bit syntax.

          There can be zero or more segments in a binary pattern. A binary pattern can │ │ │ occur wherever patterns are allowed, including inside other patterns. Binary │ │ │ patterns cannot be nested. The pattern <<>> matches a zero length binary.

          Each segment in a binary can consist of zero or more bits. A segment of type │ │ │ binary must have a size evenly divisible by 8 (or divisible by the unit size, │ │ │ if the unit size has been changed). A segment of type bitstring has no │ │ │ restrictions on the size. A segment of type float must have size 64 or 32.

          As mentioned earlier, segments have the following general syntax:

          Value:Size/TypeSpecifierList

          When matching Value, value must be either a variable or an integer, or a │ │ │ floating point literal. Expressions are not allowed.

          Size must be a │ │ │ guard expression, which can use │ │ │ -literals and previously bound variables. The following is not allowed:

          foo(N, <<X:N,T/binary>>) ->
          │ │ │ -   {X,T}.

          The two occurrences of N are not related. The compiler will complain that the │ │ │ -N in the size field is unbound.

          The correct way to write this example is as follows:

          foo(N, Bin) ->
          │ │ │ -   <<X:N,T/binary>> = Bin,
          │ │ │ -   {X,T}.

          Note

          Before OTP 23, Size was restricted to be an integer or a variable bound to │ │ │ +literals and previously bound variables. The following is not allowed:

          foo(N, <<X:N,T/binary>>) ->
          │ │ │ +   {X,T}.

          The two occurrences of N are not related. The compiler will complain that the │ │ │ +N in the size field is unbound.

          The correct way to write this example is as follows:

          foo(N, Bin) ->
          │ │ │ +   <<X:N,T/binary>> = Bin,
          │ │ │ +   {X,T}.

          Note

          Before OTP 23, Size was restricted to be an integer or a variable bound to │ │ │ an integer.

          │ │ │ │ │ │ │ │ │ │ │ │ Binding and Using a Size Variable │ │ │

          │ │ │

          There is one exception to the rule that a variable that is used as size must be │ │ │ previously bound. It is possible to match and bind a variable, and use it as a │ │ │ -size within the same binary pattern. For example:

          bar(<<Sz:8,Payload:Sz/binary-unit:8,Rest/binary>>) ->
          │ │ │ -   {Payload,Rest}.

          Here Sz is bound to the value in the first byte of the binary. Sz is then │ │ │ -used at the number of bytes to match out as a binary.

          Starting in OTP 23, the size can be a guard expression:

          bar(<<Sz:8,Payload:((Sz-1)*8)/binary,Rest/binary>>) ->
          │ │ │ -   {Payload,Rest}.

          Here Sz is the combined size of the header and the payload, so we will need to │ │ │ +size within the same binary pattern. For example:

          bar(<<Sz:8,Payload:Sz/binary-unit:8,Rest/binary>>) ->
          │ │ │ +   {Payload,Rest}.

          Here Sz is bound to the value in the first byte of the binary. Sz is then │ │ │ +used at the number of bytes to match out as a binary.

          Starting in OTP 23, the size can be a guard expression:

          bar(<<Sz:8,Payload:((Sz-1)*8)/binary,Rest/binary>>) ->
          │ │ │ +   {Payload,Rest}.

          Here Sz is the combined size of the header and the payload, so we will need to │ │ │ subtract one byte to get the size of the payload.

          │ │ │ │ │ │ │ │ │ │ │ │ Getting the Rest of the Binary or Bitstring │ │ │

          │ │ │ -

          To match out the rest of a binary, specify a binary field without size:

          foo(<<A:8,Rest/binary>>) ->

          The size of the tail must be evenly divisible by 8.

          To match out the rest of a bitstring, specify a field without size:

          foo(<<A:8,Rest/bitstring>>) ->

          There are no restrictions on the number of bits in the tail.

          │ │ │ +

          To match out the rest of a binary, specify a binary field without size:

          foo(<<A:8,Rest/binary>>) ->

          The size of the tail must be evenly divisible by 8.

          To match out the rest of a bitstring, specify a field without size:

          foo(<<A:8,Rest/bitstring>>) ->

          There are no restrictions on the number of bits in the tail.

          │ │ │ │ │ │ │ │ │ │ │ │ Appending to a Binary │ │ │

          │ │ │ -

          Appending to a binary in an efficient way can be done as follows:

          triples_to_bin(T) ->
          │ │ │ -    triples_to_bin(T, <<>>).
          │ │ │ +

          Appending to a binary in an efficient way can be done as follows:

          triples_to_bin(T) ->
          │ │ │ +    triples_to_bin(T, <<>>).
          │ │ │  
          │ │ │ -triples_to_bin([{X,Y,Z} | T], Acc) ->
          │ │ │ -    triples_to_bin(T, <<Acc/binary,X:32,Y:32,Z:32>>);
          │ │ │ -triples_to_bin([], Acc) ->
          │ │ │ +triples_to_bin([{X,Y,Z} | T], Acc) ->
          │ │ │ +    triples_to_bin(T, <<Acc/binary,X:32,Y:32,Z:32>>);
          │ │ │ +triples_to_bin([], Acc) ->
          │ │ │      Acc.
          │ │ │ │ │ │ │ │ │
          │ │ │
          │ │ │ │ │ │

          open_port/2 with │ │ │ {spawn,ExtPrg} as the first argument. The string ExtPrg is the name of the │ │ │ external program, including any command line arguments. The second argument is a │ │ │ list of options, in this case only {packet,2}. This option says that a 2 byte │ │ │ length indicator is to be used to simplify the communication between C and │ │ │ Erlang. The Erlang port automatically adds the length indicator, but this must │ │ │ be done explicitly in the external C program.

          The process is also set to trap exits, which enables detection of failure of the │ │ │ -external program:

          -module(complex1).
          │ │ │ --export([start/1, init/1]).
          │ │ │ +external program:

          -module(complex1).
          │ │ │ +-export([start/1, init/1]).
          │ │ │  
          │ │ │ -start(ExtPrg) ->
          │ │ │ -  spawn(?MODULE, init, [ExtPrg]).
          │ │ │ +start(ExtPrg) ->
          │ │ │ +  spawn(?MODULE, init, [ExtPrg]).
          │ │ │  
          │ │ │ -init(ExtPrg) ->
          │ │ │ -  register(complex, self()),
          │ │ │ -  process_flag(trap_exit, true),
          │ │ │ -  Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
          │ │ │ -  loop(Port).

          Now complex1:foo/1 and complex1:bar/1 can be implemented. Both send a │ │ │ -message to the complex process and receive the following replies:

          foo(X) ->
          │ │ │ -  call_port({foo, X}).
          │ │ │ -bar(Y) ->
          │ │ │ -  call_port({bar, Y}).
          │ │ │ +init(ExtPrg) ->
          │ │ │ +  register(complex, self()),
          │ │ │ +  process_flag(trap_exit, true),
          │ │ │ +  Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
          │ │ │ +  loop(Port).

          Now complex1:foo/1 and complex1:bar/1 can be implemented. Both send a │ │ │ +message to the complex process and receive the following replies:

          foo(X) ->
          │ │ │ +  call_port({foo, X}).
          │ │ │ +bar(Y) ->
          │ │ │ +  call_port({bar, Y}).
          │ │ │  
          │ │ │ -call_port(Msg) ->
          │ │ │ -  complex ! {call, self(), Msg},
          │ │ │ +call_port(Msg) ->
          │ │ │ +  complex ! {call, self(), Msg},
          │ │ │    receive
          │ │ │ -    {complex, Result} ->
          │ │ │ +    {complex, Result} ->
          │ │ │        Result
          │ │ │ -  end.

          The complex process does the following:

          • Encodes the message into a sequence of bytes.
          • Sends it to the port.
          • Waits for a reply.
          • Decodes the reply.
          • Sends it back to the caller:
          loop(Port) ->
          │ │ │ +  end.

          The complex process does the following:

          • Encodes the message into a sequence of bytes.
          • Sends it to the port.
          • Waits for a reply.
          • Decodes the reply.
          • Sends it back to the caller:
          loop(Port) ->
          │ │ │    receive
          │ │ │ -    {call, Caller, Msg} ->
          │ │ │ -      Port ! {self(), {command, encode(Msg)}},
          │ │ │ +    {call, Caller, Msg} ->
          │ │ │ +      Port ! {self(), {command, encode(Msg)}},
          │ │ │        receive
          │ │ │ -        {Port, {data, Data}} ->
          │ │ │ -          Caller ! {complex, decode(Data)}
          │ │ │ +        {Port, {data, Data}} ->
          │ │ │ +          Caller ! {complex, decode(Data)}
          │ │ │        end,
          │ │ │ -      loop(Port)
          │ │ │ +      loop(Port)
          │ │ │    end.

          Assuming that both the arguments and the results from the C functions are less │ │ │ than 256, a simple encoding/decoding scheme is employed. In this scheme, foo │ │ │ is represented by byte 1, bar is represented by 2, and the argument/result is │ │ │ -represented by a single byte as well:

          encode({foo, X}) -> [1, X];
          │ │ │ -encode({bar, Y}) -> [2, Y].
          │ │ │ +represented by a single byte as well:

          encode({foo, X}) -> [1, X];
          │ │ │ +encode({bar, Y}) -> [2, Y].
          │ │ │  
          │ │ │ -decode([Int]) -> Int.

          The resulting Erlang program, including functionality for stopping the port and │ │ │ -detecting port failures, is as follows:

          -module(complex1).
          │ │ │ --export([start/1, stop/0, init/1]).
          │ │ │ --export([foo/1, bar/1]).
          │ │ │ -
          │ │ │ -start(ExtPrg) ->
          │ │ │ -    spawn(?MODULE, init, [ExtPrg]).
          │ │ │ -stop() ->
          │ │ │ +decode([Int]) -> Int.

          The resulting Erlang program, including functionality for stopping the port and │ │ │ +detecting port failures, is as follows:

          -module(complex1).
          │ │ │ +-export([start/1, stop/0, init/1]).
          │ │ │ +-export([foo/1, bar/1]).
          │ │ │ +
          │ │ │ +start(ExtPrg) ->
          │ │ │ +    spawn(?MODULE, init, [ExtPrg]).
          │ │ │ +stop() ->
          │ │ │      complex ! stop.
          │ │ │  
          │ │ │ -foo(X) ->
          │ │ │ -    call_port({foo, X}).
          │ │ │ -bar(Y) ->
          │ │ │ -    call_port({bar, Y}).
          │ │ │ +foo(X) ->
          │ │ │ +    call_port({foo, X}).
          │ │ │ +bar(Y) ->
          │ │ │ +    call_port({bar, Y}).
          │ │ │  
          │ │ │ -call_port(Msg) ->
          │ │ │ -    complex ! {call, self(), Msg},
          │ │ │ +call_port(Msg) ->
          │ │ │ +    complex ! {call, self(), Msg},
          │ │ │      receive
          │ │ │ -	{complex, Result} ->
          │ │ │ +	{complex, Result} ->
          │ │ │  	    Result
          │ │ │      end.
          │ │ │  
          │ │ │ -init(ExtPrg) ->
          │ │ │ -    register(complex, self()),
          │ │ │ -    process_flag(trap_exit, true),
          │ │ │ -    Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
          │ │ │ -    loop(Port).
          │ │ │ +init(ExtPrg) ->
          │ │ │ +    register(complex, self()),
          │ │ │ +    process_flag(trap_exit, true),
          │ │ │ +    Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
          │ │ │ +    loop(Port).
          │ │ │  
          │ │ │ -loop(Port) ->
          │ │ │ +loop(Port) ->
          │ │ │      receive
          │ │ │ -	{call, Caller, Msg} ->
          │ │ │ -	    Port ! {self(), {command, encode(Msg)}},
          │ │ │ +	{call, Caller, Msg} ->
          │ │ │ +	    Port ! {self(), {command, encode(Msg)}},
          │ │ │  	    receive
          │ │ │ -		{Port, {data, Data}} ->
          │ │ │ -		    Caller ! {complex, decode(Data)}
          │ │ │ +		{Port, {data, Data}} ->
          │ │ │ +		    Caller ! {complex, decode(Data)}
          │ │ │  	    end,
          │ │ │ -	    loop(Port);
          │ │ │ +	    loop(Port);
          │ │ │  	stop ->
          │ │ │ -	    Port ! {self(), close},
          │ │ │ +	    Port ! {self(), close},
          │ │ │  	    receive
          │ │ │ -		{Port, closed} ->
          │ │ │ -		    exit(normal)
          │ │ │ +		{Port, closed} ->
          │ │ │ +		    exit(normal)
          │ │ │  	    end;
          │ │ │ -	{'EXIT', Port, Reason} ->
          │ │ │ -	    exit(port_terminated)
          │ │ │ +	{'EXIT', Port, Reason} ->
          │ │ │ +	    exit(port_terminated)
          │ │ │      end.
          │ │ │  
          │ │ │ -encode({foo, X}) -> [1, X];
          │ │ │ -encode({bar, Y}) -> [2, Y].
          │ │ │ +encode({foo, X}) -> [1, X];
          │ │ │ +encode({bar, Y}) -> [2, Y].
          │ │ │  
          │ │ │ -decode([Int]) -> Int.

          │ │ │ +decode([Int]) -> Int.

          │ │ │ │ │ │ │ │ │ │ │ │ C Program │ │ │

          │ │ │

          On the C side, it is necessary to write functions for receiving and sending data │ │ │ with 2 byte length indicators from/to Erlang. By default, the C program is to │ │ │ @@ -333,25 +333,25 @@ │ │ │ and terminates.

          │ │ │ │ │ │ │ │ │ │ │ │ Running the Example │ │ │

          │ │ │

          Step 1. Compile the C code:

          $ gcc -o extprg complex.c erl_comm.c port.c

          Step 2. Start Erlang and compile the Erlang code:

          $ erl
          │ │ │ -Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
          │ │ │ +Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
          │ │ │  
          │ │ │ -Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
          │ │ │ -1> c(complex1).
          │ │ │ -{ok,complex1}

          Step 3. Run the example:

          2> complex1:start("./extprg").
          │ │ │ +Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
          │ │ │ +1> c(complex1).
          │ │ │ +{ok,complex1}

          Step 3. Run the example:

          2> complex1:start("./extprg").
          │ │ │  <0.34.0>
          │ │ │ -3> complex1:foo(3).
          │ │ │ +3> complex1:foo(3).
          │ │ │  4
          │ │ │ -4> complex1:bar(5).
          │ │ │ +4> complex1:bar(5).
          │ │ │  10
          │ │ │ -5> complex1:stop().
          │ │ │ +5> complex1:stop().
          │ │ │  stop
          │ │ │
          │ │ │ │ │ │
          │ │ │
          │ │ │ │ │ │

          erl_ddll:load_driver/2, with the name of the shared library as │ │ │ argument.

          The port is then created using the BIF open_port/2, with the │ │ │ tuple {spawn, DriverName} as the first argument. The string SharedLib is the │ │ │ name of the port driver. The second argument is a list of options, none in this │ │ │ -case:

          -module(complex5).
          │ │ │ --export([start/1, init/1]).
          │ │ │ +case:

          -module(complex5).
          │ │ │ +-export([start/1, init/1]).
          │ │ │  
          │ │ │ -start(SharedLib) ->
          │ │ │ -    case erl_ddll:load_driver(".", SharedLib) of
          │ │ │ +start(SharedLib) ->
          │ │ │ +    case erl_ddll:load_driver(".", SharedLib) of
          │ │ │          ok -> ok;
          │ │ │ -        {error, already_loaded} -> ok;
          │ │ │ -        _ -> exit({error, could_not_load_driver})
          │ │ │ +        {error, already_loaded} -> ok;
          │ │ │ +        _ -> exit({error, could_not_load_driver})
          │ │ │      end,
          │ │ │ -    spawn(?MODULE, init, [SharedLib]).
          │ │ │ +    spawn(?MODULE, init, [SharedLib]).
          │ │ │  
          │ │ │ -init(SharedLib) ->
          │ │ │ -  register(complex, self()),
          │ │ │ -  Port = open_port({spawn, SharedLib}, []),
          │ │ │ -  loop(Port).

          Now complex5:foo/1 and complex5:bar/1 can be implemented. Both send a │ │ │ -message to the complex process and receive the following reply:

          foo(X) ->
          │ │ │ -    call_port({foo, X}).
          │ │ │ -bar(Y) ->
          │ │ │ -    call_port({bar, Y}).
          │ │ │ +init(SharedLib) ->
          │ │ │ +  register(complex, self()),
          │ │ │ +  Port = open_port({spawn, SharedLib}, []),
          │ │ │ +  loop(Port).

          Now complex5:foo/1 and complex5:bar/1 can be implemented. Both send a │ │ │ +message to the complex process and receive the following reply:

          foo(X) ->
          │ │ │ +    call_port({foo, X}).
          │ │ │ +bar(Y) ->
          │ │ │ +    call_port({bar, Y}).
          │ │ │  
          │ │ │ -call_port(Msg) ->
          │ │ │ -    complex ! {call, self(), Msg},
          │ │ │ +call_port(Msg) ->
          │ │ │ +    complex ! {call, self(), Msg},
          │ │ │      receive
          │ │ │ -        {complex, Result} ->
          │ │ │ +        {complex, Result} ->
          │ │ │              Result
          │ │ │ -    end.

          The complex process performs the following:

          • Encodes the message into a sequence of bytes.
          • Sends it to the port.
          • Waits for a reply.
          • Decodes the reply.
          • Sends it back to the caller:
          loop(Port) ->
          │ │ │ +    end.

          The complex process performs the following:

          • Encodes the message into a sequence of bytes.
          • Sends it to the port.
          • Waits for a reply.
          • Decodes the reply.
          • Sends it back to the caller:
          loop(Port) ->
          │ │ │      receive
          │ │ │ -        {call, Caller, Msg} ->
          │ │ │ -            Port ! {self(), {command, encode(Msg)}},
          │ │ │ +        {call, Caller, Msg} ->
          │ │ │ +            Port ! {self(), {command, encode(Msg)}},
          │ │ │              receive
          │ │ │ -                {Port, {data, Data}} ->
          │ │ │ -                    Caller ! {complex, decode(Data)}
          │ │ │ +                {Port, {data, Data}} ->
          │ │ │ +                    Caller ! {complex, decode(Data)}
          │ │ │              end,
          │ │ │ -            loop(Port)
          │ │ │ +            loop(Port)
          │ │ │      end.

          Assuming that both the arguments and the results from the C functions are less │ │ │ than 256, a simple encoding/decoding scheme is employed. In this scheme, foo │ │ │ is represented by byte 1, bar is represented by 2, and the argument/result is │ │ │ -represented by a single byte as well:

          encode({foo, X}) -> [1, X];
          │ │ │ -encode({bar, Y}) -> [2, Y].
          │ │ │ +represented by a single byte as well:

          encode({foo, X}) -> [1, X];
          │ │ │ +encode({bar, Y}) -> [2, Y].
          │ │ │  
          │ │ │ -decode([Int]) -> Int.

          The resulting Erlang program, including functions for stopping the port and │ │ │ +decode([Int]) -> Int.

          The resulting Erlang program, including functions for stopping the port and │ │ │ detecting port failures, is as follows:

          
          │ │ │ --module(complex5).
          │ │ │ --export([start/1, stop/0, init/1]).
          │ │ │ --export([foo/1, bar/1]).
          │ │ │ +-module(complex5).
          │ │ │ +-export([start/1, stop/0, init/1]).
          │ │ │ +-export([foo/1, bar/1]).
          │ │ │  
          │ │ │ -start(SharedLib) ->
          │ │ │ -    case erl_ddll:load_driver(".", SharedLib) of
          │ │ │ +start(SharedLib) ->
          │ │ │ +    case erl_ddll:load_driver(".", SharedLib) of
          │ │ │  	ok -> ok;
          │ │ │ -	{error, already_loaded} -> ok;
          │ │ │ -	_ -> exit({error, could_not_load_driver})
          │ │ │ +	{error, already_loaded} -> ok;
          │ │ │ +	_ -> exit({error, could_not_load_driver})
          │ │ │      end,
          │ │ │ -    spawn(?MODULE, init, [SharedLib]).
          │ │ │ +    spawn(?MODULE, init, [SharedLib]).
          │ │ │  
          │ │ │ -init(SharedLib) ->
          │ │ │ -    register(complex, self()),
          │ │ │ -    Port = open_port({spawn, SharedLib}, []),
          │ │ │ -    loop(Port).
          │ │ │ +init(SharedLib) ->
          │ │ │ +    register(complex, self()),
          │ │ │ +    Port = open_port({spawn, SharedLib}, []),
          │ │ │ +    loop(Port).
          │ │ │  
          │ │ │ -stop() ->
          │ │ │ +stop() ->
          │ │ │      complex ! stop.
          │ │ │  
          │ │ │ -foo(X) ->
          │ │ │ -    call_port({foo, X}).
          │ │ │ -bar(Y) ->
          │ │ │ -    call_port({bar, Y}).
          │ │ │ +foo(X) ->
          │ │ │ +    call_port({foo, X}).
          │ │ │ +bar(Y) ->
          │ │ │ +    call_port({bar, Y}).
          │ │ │  
          │ │ │ -call_port(Msg) ->
          │ │ │ -    complex ! {call, self(), Msg},
          │ │ │ +call_port(Msg) ->
          │ │ │ +    complex ! {call, self(), Msg},
          │ │ │      receive
          │ │ │ -	{complex, Result} ->
          │ │ │ +	{complex, Result} ->
          │ │ │  	    Result
          │ │ │      end.
          │ │ │  
          │ │ │ -loop(Port) ->
          │ │ │ +loop(Port) ->
          │ │ │      receive
          │ │ │ -	{call, Caller, Msg} ->
          │ │ │ -	    Port ! {self(), {command, encode(Msg)}},
          │ │ │ +	{call, Caller, Msg} ->
          │ │ │ +	    Port ! {self(), {command, encode(Msg)}},
          │ │ │  	    receive
          │ │ │ -		{Port, {data, Data}} ->
          │ │ │ -		    Caller ! {complex, decode(Data)}
          │ │ │ +		{Port, {data, Data}} ->
          │ │ │ +		    Caller ! {complex, decode(Data)}
          │ │ │  	    end,
          │ │ │ -	    loop(Port);
          │ │ │ +	    loop(Port);
          │ │ │  	stop ->
          │ │ │ -	    Port ! {self(), close},
          │ │ │ +	    Port ! {self(), close},
          │ │ │  	    receive
          │ │ │ -		{Port, closed} ->
          │ │ │ -		    exit(normal)
          │ │ │ +		{Port, closed} ->
          │ │ │ +		    exit(normal)
          │ │ │  	    end;
          │ │ │ -	{'EXIT', Port, Reason} ->
          │ │ │ -	    io:format("~p ~n", [Reason]),
          │ │ │ -	    exit(port_terminated)
          │ │ │ +	{'EXIT', Port, Reason} ->
          │ │ │ +	    io:format("~p ~n", [Reason]),
          │ │ │ +	    exit(port_terminated)
          │ │ │      end.
          │ │ │  
          │ │ │ -encode({foo, X}) -> [1, X];
          │ │ │ -encode({bar, Y}) -> [2, Y].
          │ │ │ +encode({foo, X}) -> [1, X];
          │ │ │ +encode({bar, Y}) -> [2, Y].
          │ │ │  
          │ │ │ -decode([Int]) -> Int.

          │ │ │ +decode([Int]) -> Int.

          │ │ │ │ │ │ │ │ │ │ │ │ C Driver │ │ │

          │ │ │

          The C driver is a module that is compiled and linked into a shared library. It │ │ │ uses a driver structure and includes the header file erl_driver.h.

          The driver structure is filled with the driver name and function pointers. It is │ │ │ @@ -347,25 +347,25 @@ │ │ │ │ │ │ │ │ │ │ │ │ Running the Example │ │ │ │ │ │

          Step 1. Compile the C code:

          unix> gcc -o example_drv.so -fpic -shared complex.c port_driver.c
          │ │ │  windows> cl -LD -MD -Fe example_drv.dll complex.c port_driver.c

          Step 2. Start Erlang and compile the Erlang code:

          > erl
          │ │ │ -Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
          │ │ │ +Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
          │ │ │  
          │ │ │ -Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
          │ │ │ -1> c(complex5).
          │ │ │ -{ok,complex5}

          Step 3. Run the example:

          2> complex5:start("example_drv").
          │ │ │ +Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
          │ │ │ +1> c(complex5).
          │ │ │ +{ok,complex5}

          Step 3. Run the example:

          2> complex5:start("example_drv").
          │ │ │  <0.34.0>
          │ │ │ -3> complex5:foo(3).
          │ │ │ +3> complex5:foo(3).
          │ │ │  4
          │ │ │ -4> complex5:bar(5).
          │ │ │ +4> complex5:bar(5).
          │ │ │  10
          │ │ │ -5> complex5:stop().
          │ │ │ +5> complex5:stop().
          │ │ │  stop
          │ │ │
          │ │ │ │ │ │
          │ │ │
          │ │ │ │ │ │ │ │ │ Compilation │ │ │ │ │ │

          Erlang programs must be compiled to object code. The compiler can generate a │ │ │ new file that contains the object code. The current abstract machine, which runs │ │ │ the object code, is called BEAM, therefore the object files get the suffix │ │ │ -.beam. The compiler can also generate a binary which can be loaded directly.

          The compiler is located in the module compile in Compiler.

          compile:file(Module)
          │ │ │ -compile:file(Module, Options)

          The Erlang shell understands the command c(Module), which both compiles and │ │ │ +.beam. The compiler can also generate a binary which can be loaded directly.

          The compiler is located in the module compile in Compiler.

          compile:file(Module)
          │ │ │ +compile:file(Module, Options)

          The Erlang shell understands the command c(Module), which both compiles and │ │ │ loads Module.

          There is also a module make, which provides a set of functions similar to the │ │ │ UNIX type Make functions, see module make in Tools.

          The compiler can also be accessed from the OS prompt using the │ │ │ erl executable in ERTS.

          % erl -compile Module1...ModuleN
          │ │ │  % erl -make

          The erlc program provides way to compile modules from the OS │ │ │ shell, see the erlc executable in ERTS. It │ │ │ understands a number of flags that can be used to define macros, add search │ │ │ paths for include files, and more.

          % erlc <flags> File1.erl...FileN.erl

          │ │ │ @@ -156,54 +156,54 @@ │ │ │ When a module is loaded into the system for the first time, the code becomes │ │ │ 'current'. If then a new instance of the module is loaded, the code of the │ │ │ previous instance becomes 'old' and the new instance becomes 'current'.

          Both old and current code is valid, and can be evaluated concurrently. Fully │ │ │ qualified function calls always refer to current code. Old code can still be │ │ │ evaluated because of processes lingering in the old code.

          If a third instance of the module is loaded, the code server removes (purges) │ │ │ the old code and any processes lingering in it is terminated. Then the third │ │ │ instance becomes 'current' and the previously current code becomes 'old'.

          To change from old code to current code, a process must make a fully qualified │ │ │ -function call.

          Example:

          -module(m).
          │ │ │ --export([loop/0]).
          │ │ │ +function call.

          Example:

          -module(m).
          │ │ │ +-export([loop/0]).
          │ │ │  
          │ │ │ -loop() ->
          │ │ │ +loop() ->
          │ │ │      receive
          │ │ │          code_switch ->
          │ │ │ -            m:loop();
          │ │ │ +            m:loop();
          │ │ │          Msg ->
          │ │ │              ...
          │ │ │ -            loop()
          │ │ │ +            loop()
          │ │ │      end.

          To make the process change code, send the message code_switch to it. The │ │ │ process then makes a fully qualified call to m:loop() and changes to current │ │ │ code. Notice that m:loop/0 must be exported.

          For code replacement of funs to work, use the syntax │ │ │ fun Module:FunctionName/Arity.

          │ │ │ │ │ │ │ │ │ │ │ │ Running a Function When a Module is Loaded │ │ │

          │ │ │

          The -on_load() directive names a function that is to be run automatically when │ │ │ -a module is loaded.

          Its syntax is as follows:

          -on_load(Name/0).

          It is not necessary to export the function. It is called in a freshly spawned │ │ │ +a module is loaded.

          Its syntax is as follows:

          -on_load(Name/0).

          It is not necessary to export the function. It is called in a freshly spawned │ │ │ process (which terminates as soon as the function returns).

          The function must return ok if the module is to become the new current code │ │ │ for the module and become callable.

          Returning any other value or generating an exception causes the new code to be │ │ │ unloaded. If the return value is not an atom, a warning error report is sent to │ │ │ the error logger.

          If there already is current code for the module, that code will remain current │ │ │ and can be called until the on_load function has returned. If the on_load │ │ │ function fails, the current code (if any) will remain current. If there is no │ │ │ current code for a module, any process that makes an external call to the module │ │ │ before the on_load function has finished will be suspended until the on_load │ │ │ function have finished.

          Change

          Before Erlang/OTP 19, if the on_load function failed, any previously current │ │ │ code would become old, essentially leaving the system without any working and │ │ │ reachable instance of the module.

          In embedded mode, first all modules are loaded. Then all on_load functions are │ │ │ called. The system is terminated unless all of the on_load functions return │ │ │ -ok.

          Example:

          -module(m).
          │ │ │ --on_load(load_my_nifs/0).
          │ │ │ +ok.

          Example:

          -module(m).
          │ │ │ +-on_load(load_my_nifs/0).
          │ │ │  
          │ │ │ -load_my_nifs() ->
          │ │ │ +load_my_nifs() ->
          │ │ │      NifPath = ...,    %Set up the path to the NIF library.
          │ │ │      Info = ...,       %Initialize the Info term
          │ │ │ -    erlang:load_nif(NifPath, Info).

          If the call to erlang:load_nif/2 fails, the module is unloaded and a warning │ │ │ + erlang:load_nif(NifPath, Info).

          If the call to erlang:load_nif/2 fails, the module is unloaded and a warning │ │ │ report is sent to the error loader.

          │ │ │

          │ │ │ │ │ │
          │ │ │
          │ │ │ │ │ │

          │ │ │ │ │ │ │ │ │ Operator ++ │ │ │

          │ │ │

          The ++ operator copies its left-hand side operand. That is clearly │ │ │ -seen if we do our own implementation in Erlang:

          my_plus_plus([H|T], Tail) ->
          │ │ │ -    [H|my_plus_plus(T, Tail)];
          │ │ │ -my_plus_plus([], Tail) ->
          │ │ │ -    Tail.

          We must be careful how we use ++ in a loop. First is how not to use it:

          DO NOT

          naive_reverse([H|T]) ->
          │ │ │ -    naive_reverse(T) ++ [H];
          │ │ │ -naive_reverse([]) ->
          │ │ │ -    [].

          As the ++ operator copies its left-hand side operand, the growing │ │ │ -result is copied repeatedly, leading to quadratic complexity.

          On the other hand, using ++ in loop like this is perfectly fine:

          OK

          naive_but_ok_reverse(List) ->
          │ │ │ -    naive_but_ok_reverse(List, []).
          │ │ │ +seen if we do our own implementation in Erlang:

          my_plus_plus([H|T], Tail) ->
          │ │ │ +    [H|my_plus_plus(T, Tail)];
          │ │ │ +my_plus_plus([], Tail) ->
          │ │ │ +    Tail.

          We must be careful how we use ++ in a loop. First is how not to use it:

          DO NOT

          naive_reverse([H|T]) ->
          │ │ │ +    naive_reverse(T) ++ [H];
          │ │ │ +naive_reverse([]) ->
          │ │ │ +    [].

          As the ++ operator copies its left-hand side operand, the growing │ │ │ +result is copied repeatedly, leading to quadratic complexity.

          On the other hand, using ++ in loop like this is perfectly fine:

          OK

          naive_but_ok_reverse(List) ->
          │ │ │ +    naive_but_ok_reverse(List, []).
          │ │ │  
          │ │ │ -naive_but_ok_reverse([H|T], Acc) ->
          │ │ │ -    naive_but_ok_reverse(T, [H] ++ Acc);
          │ │ │ -naive_but_ok_reverse([], Acc) ->
          │ │ │ +naive_but_ok_reverse([H|T], Acc) ->
          │ │ │ +    naive_but_ok_reverse(T, [H] ++ Acc);
          │ │ │ +naive_but_ok_reverse([], Acc) ->
          │ │ │      Acc.

          Each list element is copied only once. The growing result Acc is the right-hand │ │ │ -side operand, which it is not copied.

          Experienced Erlang programmers would probably write as follows:

          DO

          vanilla_reverse([H|T], Acc) ->
          │ │ │ -    vanilla_reverse(T, [H|Acc]);
          │ │ │ -vanilla_reverse([], Acc) ->
          │ │ │ +side operand, which it is not copied.

          Experienced Erlang programmers would probably write as follows:

          DO

          vanilla_reverse([H|T], Acc) ->
          │ │ │ +    vanilla_reverse(T, [H|Acc]);
          │ │ │ +vanilla_reverse([], Acc) ->
          │ │ │      Acc.

          In principle, this is slightly more efficient because the list element [H] │ │ │ is not built before being copied and discarded. In practice, the compiler │ │ │ rewrites [H] ++ Acc to [H|Acc].

          │ │ │ │ │ │ │ │ │ │ │ │ Timer Module │ │ │ @@ -160,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=Shared}.

          In the process that calls init2/0, the size of the data field in the state │ │ │ +of a term with a shared subterm:

          {SubTerm, SubTerm}

          When a term is copied to another process, sharing of subterms will be lost and │ │ │ +the copied term can be many times larger than the original term. For example:

          init2() ->
          │ │ │ +    SharedSubTerms = lists:foldl(fun(_, A) -> [A|A] end, [0], lists:seq(1, 15)),
          │ │ │ +    #state{data=Shared}.

          In the process that calls init2/0, the size of the data field in the state │ │ │ record will be 32 heap words. When the record is copied to the newly created │ │ │ process, sharing will be lost and the size of the copied data field will be │ │ │ 131070 heap words. More details about │ │ │ loss off sharing are found in a later │ │ │ section.

          To avoid the problem, outside of the fun extract only the fields of the record │ │ │ -that are actually used:

          DO

          fixed_accidental1(State) ->
          │ │ │ +that are actually used:

          DO

          fixed_accidental1(State) ->
          │ │ │      Info = State#state.info,
          │ │ │ -    spawn(fun() ->
          │ │ │ -                  io:format("~p\n", [Info])
          │ │ │ -          end).

          Similarly, outside of the fun extract only the map elements that are actually │ │ │ -used:

          DO

          fixed_accidental2(State) ->
          │ │ │ -    Info = map_get(info, State),
          │ │ │ -    spawn(fun() ->
          │ │ │ -                  io:format("~p\n", [Info])
          │ │ │ -          end).

          │ │ │ + spawn(fun() -> │ │ │ + io:format("~p\n", [Info]) │ │ │ + end).

          Similarly, outside of the fun extract only the map elements that are actually │ │ │ +used:

          DO

          fixed_accidental2(State) ->
          │ │ │ +    Info = map_get(info, State),
          │ │ │ +    spawn(fun() ->
          │ │ │ +                  io:format("~p\n", [Info])
          │ │ │ +          end).

          │ │ │ │ │ │ │ │ │ │ │ │ list_to_atom/1, 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 second argument. The function start starts two Erlang │ │ │ processes, one that writes "hello" three times and one that writes "goodbye" │ │ │ three times. Both processes use the function say_something. Notice that a │ │ │ function used in this way by spawn, to start a process, must be exported from │ │ │ -the module (that is, in the -export at the start of the module).

          9> tut14:start().
          │ │ │ +the module (that is, in the -export at the start of the module).

          9> tut14:start().
          │ │ │  hello
          │ │ │  goodbye
          │ │ │  <0.63.0>
          │ │ │  hello
          │ │ │  goodbye
          │ │ │  hello
          │ │ │  goodbye

          Notice that it did not write "hello" three times and then "goodbye" three times. │ │ │ Instead, the first process wrote a "hello", the second a "goodbye", the first │ │ │ another "hello" and so forth. But where did the <0.63.0> come from? The return │ │ │ value of a function is the return value of the last "thing" in the function. The │ │ │ -last thing in the function start is

          spawn(tut14, say_something, [goodbye, 3]).

          spawn returns a process identifier, or pid, which uniquely identifies the │ │ │ +last thing in the function start is

          spawn(tut14, say_something, [goodbye, 3]).

          spawn returns a process identifier, or pid, which uniquely identifies the │ │ │ process. So <0.63.0> is the pid of the spawn function call above. The next │ │ │ example shows how to use pids.

          Notice also that ~p is used instead of ~w in io:format/2. To quote the manual:

          ~p Writes the data with standard syntax in the same way as ~w, but breaks terms │ │ │ whose printed representation is longer than one line into many lines and indents │ │ │ each line sensibly. It also tries to detect flat lists of printable characters and │ │ │ to output these as strings

          │ │ │ │ │ │ │ │ │ │ │ │ Message Passing │ │ │

          │ │ │

          In the following example two processes are created and they send messages to │ │ │ -each other a number of times.

          -module(tut15).
          │ │ │ +each other a number of times.

          -module(tut15).
          │ │ │  
          │ │ │ --export([start/0, ping/2, pong/0]).
          │ │ │ +-export([start/0, ping/2, pong/0]).
          │ │ │  
          │ │ │ -ping(0, Pong_PID) ->
          │ │ │ +ping(0, Pong_PID) ->
          │ │ │      Pong_PID ! finished,
          │ │ │ -    io:format("ping finished~n", []);
          │ │ │ +    io:format("ping finished~n", []);
          │ │ │  
          │ │ │ -ping(N, Pong_PID) ->
          │ │ │ -    Pong_PID ! {ping, self()},
          │ │ │ +ping(N, Pong_PID) ->
          │ │ │ +    Pong_PID ! {ping, self()},
          │ │ │      receive
          │ │ │          pong ->
          │ │ │ -            io:format("Ping received pong~n", [])
          │ │ │ +            io:format("Ping received pong~n", [])
          │ │ │      end,
          │ │ │ -    ping(N - 1, Pong_PID).
          │ │ │ +    ping(N - 1, Pong_PID).
          │ │ │  
          │ │ │ -pong() ->
          │ │ │ +pong() ->
          │ │ │      receive
          │ │ │          finished ->
          │ │ │ -            io:format("Pong finished~n", []);
          │ │ │ -        {ping, Ping_PID} ->
          │ │ │ -            io:format("Pong received ping~n", []),
          │ │ │ +            io:format("Pong finished~n", []);
          │ │ │ +        {ping, Ping_PID} ->
          │ │ │ +            io:format("Pong received ping~n", []),
          │ │ │              Ping_PID ! pong,
          │ │ │ -            pong()
          │ │ │ +            pong()
          │ │ │      end.
          │ │ │  
          │ │ │ -start() ->
          │ │ │ -    Pong_PID = spawn(tut15, pong, []),
          │ │ │ -    spawn(tut15, ping, [3, Pong_PID]).
          1> c(tut15).
          │ │ │ -{ok,tut15}
          │ │ │ -2> tut15: start().
          │ │ │ +start() ->
          │ │ │ +    Pong_PID = spawn(tut15, pong, []),
          │ │ │ +    spawn(tut15, ping, [3, Pong_PID]).
          1> c(tut15).
          │ │ │ +{ok,tut15}
          │ │ │ +2> tut15: start().
          │ │ │  <0.36.0>
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  ping finished
          │ │ │ -Pong finished

          The function start first creates a process, let us call it "pong":

          Pong_PID = spawn(tut15, pong, [])

          This process executes tut15:pong(). Pong_PID is the process identity of the │ │ │ -"pong" process. The function start now creates another process "ping":

          spawn(tut15, ping, [3, Pong_PID]),

          This process executes:

          tut15:ping(3, Pong_PID)

          <0.36.0> is the return value from the start function.

          The process "pong" now does:

          receive
          │ │ │ +Pong finished

          The function start first creates a process, let us call it "pong":

          Pong_PID = spawn(tut15, pong, [])

          This process executes tut15:pong(). Pong_PID is the process identity of the │ │ │ +"pong" process. The function start now creates another process "ping":

          spawn(tut15, ping, [3, Pong_PID]),

          This process executes:

          tut15:ping(3, Pong_PID)

          <0.36.0> is the return value from the start function.

          The process "pong" now does:

          receive
          │ │ │      finished ->
          │ │ │ -        io:format("Pong finished~n", []);
          │ │ │ -    {ping, Ping_PID} ->
          │ │ │ -        io:format("Pong received ping~n", []),
          │ │ │ +        io:format("Pong finished~n", []);
          │ │ │ +    {ping, Ping_PID} ->
          │ │ │ +        io:format("Pong received ping~n", []),
          │ │ │          Ping_PID ! pong,
          │ │ │ -        pong()
          │ │ │ +        pong()
          │ │ │  end.

          The receive construct is used to allow processes to wait for messages from │ │ │ other processes. It has the following format:

          receive
          │ │ │     pattern1 ->
          │ │ │         actions1;
          │ │ │     pattern2 ->
          │ │ │         actions2;
          │ │ │     ....
          │ │ │ @@ -253,84 +253,84 @@
          │ │ │  queue (keeping the first message and any other messages in the queue). If the
          │ │ │  second message does not match, the third message is tried, and so on, until the
          │ │ │  end of the queue is reached. If the end of the queue is reached, the process
          │ │ │  blocks (stops execution) and waits until a new message is received and this
          │ │ │  procedure is repeated.

          The Erlang implementation is "clever" and minimizes the number of times each │ │ │ message is tested against the patterns in each receive.

          Now back to the ping pong example.

          "Pong" is waiting for messages. If the atom finished is received, "pong" │ │ │ writes "Pong finished" to the output and, as it has nothing more to do, │ │ │ -terminates. If it receives a message with the format:

          {ping, Ping_PID}

          it writes "Pong received ping" to the output and sends the atom pong to the │ │ │ +terminates. If it receives a message with the format:

          {ping, Ping_PID}

          it writes "Pong received ping" to the output and sends the atom pong to the │ │ │ process "ping":

          Ping_PID ! pong

          Notice how the operator "!" is used to send messages. The syntax of "!" is:

          Pid ! Message

          That is, Message (any Erlang term) is sent to the process with identity Pid.

          After sending the message pong to the process "ping", "pong" calls the pong │ │ │ function again, which causes it to get back to the receive again and wait for │ │ │ -another message.

          Now let us look at the process "ping". Recall that it was started by executing:

          tut15:ping(3, Pong_PID)

          Looking at the function ping/2, the second clause of ping/2 is executed │ │ │ +another message.

          Now let us look at the process "ping". Recall that it was started by executing:

          tut15:ping(3, Pong_PID)

          Looking at the function ping/2, the second clause of ping/2 is executed │ │ │ since the value of the first argument is 3 (not 0) (first clause head is │ │ │ -ping(0,Pong_PID), second clause head is ping(N,Pong_PID), so N becomes 3).

          The second clause sends a message to "pong":

          Pong_PID ! {ping, self()},

          self/0 returns the pid of the process that executes self/0, in this case the │ │ │ +ping(0,Pong_PID), second clause head is ping(N,Pong_PID), so N becomes 3).

          The second clause sends a message to "pong":

          Pong_PID ! {ping, self()},

          self/0 returns the pid of the process that executes self/0, in this case the │ │ │ pid of "ping". (Recall the code for "pong", this lands up in the variable │ │ │ Ping_PID in the receive previously explained.)

          "Ping" now waits for a reply from "pong":

          receive
          │ │ │      pong ->
          │ │ │ -        io:format("Ping received pong~n", [])
          │ │ │ +        io:format("Ping received pong~n", [])
          │ │ │  end,

          It writes "Ping received pong" when this reply arrives, after which "ping" calls │ │ │ -the ping function again.

          ping(N - 1, Pong_PID)

          N-1 causes the first argument to be decremented until it becomes 0. When this │ │ │ -occurs, the first clause of ping/2 is executed:

          ping(0, Pong_PID) ->
          │ │ │ +the ping function again.

          ping(N - 1, Pong_PID)

          N-1 causes the first argument to be decremented until it becomes 0. When this │ │ │ +occurs, the first clause of ping/2 is executed:

          ping(0, Pong_PID) ->
          │ │ │      Pong_PID !  finished,
          │ │ │ -    io:format("ping finished~n", []);

          The atom finished is sent to "pong" (causing it to terminate as described │ │ │ + io:format("ping finished~n", []);

          The atom finished is sent to "pong" (causing it to terminate as described │ │ │ above) and "ping finished" is written to the output. "Ping" then terminates as │ │ │ it has nothing left to do.

          │ │ │ │ │ │ │ │ │ │ │ │ Registered Process Names │ │ │

          │ │ │

          In the above example, "pong" was first created to be able to give the identity │ │ │ of "pong" when "ping" was started. That is, in some way "ping" must be able to │ │ │ know the identity of "pong" to be able to send a message to it. Sometimes │ │ │ processes which need to know each other's identities are started independently │ │ │ of each other. Erlang thus provides a mechanism for processes to be given names │ │ │ so that these names can be used as identities instead of pids. This is done by │ │ │ -using the register BIF:

          register(some_atom, Pid)

          Let us now rewrite the ping pong example using this and give the name pong to │ │ │ -the "pong" process:

          -module(tut16).
          │ │ │ +using the register BIF:

          register(some_atom, Pid)

          Let us now rewrite the ping pong example using this and give the name pong to │ │ │ +the "pong" process:

          -module(tut16).
          │ │ │  
          │ │ │ --export([start/0, ping/1, pong/0]).
          │ │ │ +-export([start/0, ping/1, pong/0]).
          │ │ │  
          │ │ │ -ping(0) ->
          │ │ │ +ping(0) ->
          │ │ │      pong ! finished,
          │ │ │ -    io:format("ping finished~n", []);
          │ │ │ +    io:format("ping finished~n", []);
          │ │ │  
          │ │ │ -ping(N) ->
          │ │ │ -    pong ! {ping, self()},
          │ │ │ +ping(N) ->
          │ │ │ +    pong ! {ping, self()},
          │ │ │      receive
          │ │ │          pong ->
          │ │ │ -            io:format("Ping received pong~n", [])
          │ │ │ +            io:format("Ping received pong~n", [])
          │ │ │      end,
          │ │ │ -    ping(N - 1).
          │ │ │ +    ping(N - 1).
          │ │ │  
          │ │ │ -pong() ->
          │ │ │ +pong() ->
          │ │ │      receive
          │ │ │          finished ->
          │ │ │ -            io:format("Pong finished~n", []);
          │ │ │ -        {ping, Ping_PID} ->
          │ │ │ -            io:format("Pong received ping~n", []),
          │ │ │ +            io:format("Pong finished~n", []);
          │ │ │ +        {ping, Ping_PID} ->
          │ │ │ +            io:format("Pong received ping~n", []),
          │ │ │              Ping_PID ! pong,
          │ │ │ -            pong()
          │ │ │ +            pong()
          │ │ │      end.
          │ │ │  
          │ │ │ -start() ->
          │ │ │ -    register(pong, spawn(tut16, pong, [])),
          │ │ │ -    spawn(tut16, ping, [3]).
          2> c(tut16).
          │ │ │ -{ok, tut16}
          │ │ │ -3> tut16:start().
          │ │ │ +start() ->
          │ │ │ +    register(pong, spawn(tut16, pong, [])),
          │ │ │ +    spawn(tut16, ping, [3]).
          2> c(tut16).
          │ │ │ +{ok, tut16}
          │ │ │ +3> tut16:start().
          │ │ │  <0.38.0>
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  ping finished
          │ │ │ -Pong finished

          Here the start/0 function,

          register(pong, spawn(tut16, pong, [])),

          both spawns the "pong" process and gives it the name pong. In the "ping" │ │ │ -process, messages can be sent to pong by:

          pong ! {ping, self()},

          ping/2 now becomes ping/1 as the argument Pong_PID is not needed.

          │ │ │ +Pong finished

          Here the start/0 function,

          register(pong, spawn(tut16, pong, [])),

          both spawns the "pong" process and gives it the name pong. In the "ping" │ │ │ +process, messages can be sent to pong by:

          pong ! {ping, self()},

          ping/2 now becomes ping/1 as the argument Pong_PID is not needed.

          │ │ │ │ │ │ │ │ │ │ │ │ Distributed Programming │ │ │

          │ │ │

          Let us rewrite the ping pong program with "ping" and "pong" on different │ │ │ computers. First a few things are needed to set up to get this to work. The │ │ │ @@ -350,106 +350,106 @@ │ │ │ of the file. This is a requirement.

          When you start an Erlang system that is going to talk to other Erlang systems, │ │ │ you must give it a name, for example:

          $ erl -sname my_name

          We will see more details of this later. If you want to experiment with │ │ │ distributed Erlang, but you only have one computer to work on, you can start two │ │ │ separate Erlang systems on the same computer but give them different names. Each │ │ │ Erlang system running on a computer is called an Erlang node.

          (Note: erl -sname assumes that all nodes are in the same IP domain and we can │ │ │ use only the first component of the IP address, if we want to use nodes in │ │ │ different domains we use -name instead, but then all IP address must be given │ │ │ -in full.)

          Here is the ping pong example modified to run on two separate nodes:

          -module(tut17).
          │ │ │ +in full.)

          Here is the ping pong example modified to run on two separate nodes:

          -module(tut17).
          │ │ │  
          │ │ │ --export([start_ping/1, start_pong/0,  ping/2, pong/0]).
          │ │ │ +-export([start_ping/1, start_pong/0,  ping/2, pong/0]).
          │ │ │  
          │ │ │ -ping(0, Pong_Node) ->
          │ │ │ -    {pong, Pong_Node} ! finished,
          │ │ │ -    io:format("ping finished~n", []);
          │ │ │ +ping(0, Pong_Node) ->
          │ │ │ +    {pong, Pong_Node} ! finished,
          │ │ │ +    io:format("ping finished~n", []);
          │ │ │  
          │ │ │ -ping(N, Pong_Node) ->
          │ │ │ -    {pong, Pong_Node} ! {ping, self()},
          │ │ │ +ping(N, Pong_Node) ->
          │ │ │ +    {pong, Pong_Node} ! {ping, self()},
          │ │ │      receive
          │ │ │          pong ->
          │ │ │ -            io:format("Ping received pong~n", [])
          │ │ │ +            io:format("Ping received pong~n", [])
          │ │ │      end,
          │ │ │ -    ping(N - 1, Pong_Node).
          │ │ │ +    ping(N - 1, Pong_Node).
          │ │ │  
          │ │ │ -pong() ->
          │ │ │ +pong() ->
          │ │ │      receive
          │ │ │          finished ->
          │ │ │ -            io:format("Pong finished~n", []);
          │ │ │ -        {ping, Ping_PID} ->
          │ │ │ -            io:format("Pong received ping~n", []),
          │ │ │ +            io:format("Pong finished~n", []);
          │ │ │ +        {ping, Ping_PID} ->
          │ │ │ +            io:format("Pong received ping~n", []),
          │ │ │              Ping_PID ! pong,
          │ │ │ -            pong()
          │ │ │ +            pong()
          │ │ │      end.
          │ │ │  
          │ │ │ -start_pong() ->
          │ │ │ -    register(pong, spawn(tut17, pong, [])).
          │ │ │ +start_pong() ->
          │ │ │ +    register(pong, spawn(tut17, pong, [])).
          │ │ │  
          │ │ │ -start_ping(Pong_Node) ->
          │ │ │ -    spawn(tut17, ping, [3, Pong_Node]).

          Let us assume there are two computers called gollum and kosken. First a node is │ │ │ +start_ping(Pong_Node) -> │ │ │ + spawn(tut17, ping, [3, Pong_Node]).

          Let us assume there are two computers called gollum and kosken. First a node is │ │ │ started on kosken, called ping, and then a node on gollum, called pong.

          On kosken (on a Linux/UNIX system):

          kosken> erl -sname ping
          │ │ │  Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0]
          │ │ │  
          │ │ │  Eshell V5.2.3.7  (abort with ^G)
          │ │ │  (ping@kosken)1>

          On gollum:

          gollum> erl -sname pong
          │ │ │  Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0]
          │ │ │  
          │ │ │  Eshell V5.2.3.7  (abort with ^G)
          │ │ │ -(pong@gollum)1>

          Now the "pong" process on gollum is started:

          (pong@gollum)1> tut17:start_pong().
          │ │ │ +(pong@gollum)1>

          Now the "pong" process on gollum is started:

          (pong@gollum)1> tut17:start_pong().
          │ │ │  true

          And the "ping" process on kosken is started (from the code above you can see │ │ │ that a parameter of the start_ping function is the node name of the Erlang │ │ │ -system where "pong" is running):

          (ping@kosken)1> tut17:start_ping(pong@gollum).
          │ │ │ +system where "pong" is running):

          (ping@kosken)1> tut17:start_ping(pong@gollum).
          │ │ │  <0.37.0>
          │ │ │  Ping received pong
          │ │ │  Ping received pong
          │ │ │  Ping received pong
          │ │ │  ping finished

          As shown, the ping pong program has run. On the "pong" side:

          (pong@gollum)2> 
          │ │ │  Pong received ping
          │ │ │  Pong received ping
          │ │ │  Pong received ping
          │ │ │  Pong finished
          │ │ │ -(pong@gollum)2> 

          Looking at the tut17 code, you see that the pong function itself is │ │ │ +(pong@gollum)2>

          Looking at the tut17 code, you see that the pong function itself is │ │ │ unchanged, the following lines work in the same way irrespective of on which │ │ │ -node the "ping" process is executes:

          {ping, Ping_PID} ->
          │ │ │ -    io:format("Pong received ping~n", []),
          │ │ │ +node the "ping" process is executes:

          {ping, Ping_PID} ->
          │ │ │ +    io:format("Pong received ping~n", []),
          │ │ │      Ping_PID ! pong,

          Thus, Erlang pids contain information about where the process executes. So if │ │ │ you know the pid of a process, the ! operator can be used to send it a │ │ │ -message disregarding if the process is on the same node or on a different node.

          A difference is how messages are sent to a registered process on another node:

          {pong, Pong_Node} ! {ping, self()},

          A tuple {registered_name,node_name} is used instead of just the │ │ │ +message disregarding if the process is on the same node or on a different node.

          A difference is how messages are sent to a registered process on another node:

          {pong, Pong_Node} ! {ping, self()},

          A tuple {registered_name,node_name} is used instead of just the │ │ │ registered_name.

          In the previous example, "ping" and "pong" were started from the shells of two │ │ │ separate Erlang nodes. spawn can also be used to start processes in other │ │ │ nodes.

          The next example is the ping pong program, yet again, but this time "ping" is │ │ │ -started in another node:

          -module(tut18).
          │ │ │ +started in another node:

          -module(tut18).
          │ │ │  
          │ │ │ --export([start/1,  ping/2, pong/0]).
          │ │ │ +-export([start/1,  ping/2, pong/0]).
          │ │ │  
          │ │ │ -ping(0, Pong_Node) ->
          │ │ │ -    {pong, Pong_Node} ! finished,
          │ │ │ -    io:format("ping finished~n", []);
          │ │ │ +ping(0, Pong_Node) ->
          │ │ │ +    {pong, Pong_Node} ! finished,
          │ │ │ +    io:format("ping finished~n", []);
          │ │ │  
          │ │ │ -ping(N, Pong_Node) ->
          │ │ │ -    {pong, Pong_Node} ! {ping, self()},
          │ │ │ +ping(N, Pong_Node) ->
          │ │ │ +    {pong, Pong_Node} ! {ping, self()},
          │ │ │      receive
          │ │ │          pong ->
          │ │ │ -            io:format("Ping received pong~n", [])
          │ │ │ +            io:format("Ping received pong~n", [])
          │ │ │      end,
          │ │ │ -    ping(N - 1, Pong_Node).
          │ │ │ +    ping(N - 1, Pong_Node).
          │ │ │  
          │ │ │ -pong() ->
          │ │ │ +pong() ->
          │ │ │      receive
          │ │ │          finished ->
          │ │ │ -            io:format("Pong finished~n", []);
          │ │ │ -        {ping, Ping_PID} ->
          │ │ │ -            io:format("Pong received ping~n", []),
          │ │ │ +            io:format("Pong finished~n", []);
          │ │ │ +        {ping, Ping_PID} ->
          │ │ │ +            io:format("Pong received ping~n", []),
          │ │ │              Ping_PID ! pong,
          │ │ │ -            pong()
          │ │ │ +            pong()
          │ │ │      end.
          │ │ │  
          │ │ │ -start(Ping_Node) ->
          │ │ │ -    register(pong, spawn(tut18, pong, [])),
          │ │ │ -    spawn(Ping_Node, tut18, ping, [3, node()]).

          Assuming an Erlang system called ping (but not the "ping" process) has already │ │ │ -been started on kosken, then on gollum this is done:

          (pong@gollum)1> tut18:start(ping@kosken).
          │ │ │ +start(Ping_Node) ->
          │ │ │ +    register(pong, spawn(tut18, pong, [])),
          │ │ │ +    spawn(Ping_Node, tut18, ping, [3, node()]).

          Assuming an Erlang system called ping (but not the "ping" process) has already │ │ │ +been started on kosken, then on gollum this is done:

          (pong@gollum)1> tut18:start(ping@kosken).
          │ │ │  <3934.39.0>
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │ @@ -516,188 +516,188 @@
          │ │ │  %%% Started: messenger:client(Server_Node, Name)
          │ │ │  %%% To client: logoff
          │ │ │  %%% To client: {message_to, ToName, Message}
          │ │ │  %%%
          │ │ │  %%% Configuration: change the server_node() function to return the
          │ │ │  %%% name of the node where the messenger server runs
          │ │ │  
          │ │ │ --module(messenger).
          │ │ │ --export([start_server/0, server/1, logon/1, logoff/0, message/2, client/2]).
          │ │ │ +-module(messenger).
          │ │ │ +-export([start_server/0, server/1, logon/1, logoff/0, message/2, client/2]).
          │ │ │  
          │ │ │  %%% Change the function below to return the name of the node where the
          │ │ │  %%% messenger server runs
          │ │ │ -server_node() ->
          │ │ │ +server_node() ->
          │ │ │      messenger@super.
          │ │ │  
          │ │ │  %%% This is the server process for the "messenger"
          │ │ │  %%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
          │ │ │ -server(User_List) ->
          │ │ │ +server(User_List) ->
          │ │ │      receive
          │ │ │ -        {From, logon, Name} ->
          │ │ │ -            New_User_List = server_logon(From, Name, User_List),
          │ │ │ -            server(New_User_List);
          │ │ │ -        {From, logoff} ->
          │ │ │ -            New_User_List = server_logoff(From, User_List),
          │ │ │ -            server(New_User_List);
          │ │ │ -        {From, message_to, To, Message} ->
          │ │ │ -            server_transfer(From, To, Message, User_List),
          │ │ │ -            io:format("list is now: ~p~n", [User_List]),
          │ │ │ -            server(User_List)
          │ │ │ +        {From, logon, Name} ->
          │ │ │ +            New_User_List = server_logon(From, Name, User_List),
          │ │ │ +            server(New_User_List);
          │ │ │ +        {From, logoff} ->
          │ │ │ +            New_User_List = server_logoff(From, User_List),
          │ │ │ +            server(New_User_List);
          │ │ │ +        {From, message_to, To, Message} ->
          │ │ │ +            server_transfer(From, To, Message, User_List),
          │ │ │ +            io:format("list is now: ~p~n", [User_List]),
          │ │ │ +            server(User_List)
          │ │ │      end.
          │ │ │  
          │ │ │  %%% Start the server
          │ │ │ -start_server() ->
          │ │ │ -    register(messenger, spawn(messenger, server, [[]])).
          │ │ │ +start_server() ->
          │ │ │ +    register(messenger, spawn(messenger, server, [[]])).
          │ │ │  
          │ │ │  
          │ │ │  %%% Server adds a new user to the user list
          │ │ │ -server_logon(From, Name, User_List) ->
          │ │ │ +server_logon(From, Name, User_List) ->
          │ │ │      %% check if logged on anywhere else
          │ │ │ -    case lists:keymember(Name, 2, User_List) of
          │ │ │ +    case lists:keymember(Name, 2, User_List) of
          │ │ │          true ->
          │ │ │ -            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
          │ │ │ +            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
          │ │ │              User_List;
          │ │ │          false ->
          │ │ │ -            From ! {messenger, logged_on},
          │ │ │ -            [{From, Name} | User_List]        %add user to the list
          │ │ │ +            From ! {messenger, logged_on},
          │ │ │ +            [{From, Name} | User_List]        %add user to the list
          │ │ │      end.
          │ │ │  
          │ │ │  %%% Server deletes a user from the user list
          │ │ │ -server_logoff(From, User_List) ->
          │ │ │ -    lists:keydelete(From, 1, User_List).
          │ │ │ +server_logoff(From, User_List) ->
          │ │ │ +    lists:keydelete(From, 1, User_List).
          │ │ │  
          │ │ │  
          │ │ │  %%% Server transfers a message between user
          │ │ │ -server_transfer(From, To, Message, User_List) ->
          │ │ │ +server_transfer(From, To, Message, User_List) ->
          │ │ │      %% check that the user is logged on and who he is
          │ │ │ -    case lists:keysearch(From, 1, User_List) of
          │ │ │ +    case lists:keysearch(From, 1, User_List) of
          │ │ │          false ->
          │ │ │ -            From ! {messenger, stop, you_are_not_logged_on};
          │ │ │ -        {value, {From, Name}} ->
          │ │ │ -            server_transfer(From, Name, To, Message, User_List)
          │ │ │ +            From ! {messenger, stop, you_are_not_logged_on};
          │ │ │ +        {value, {From, Name}} ->
          │ │ │ +            server_transfer(From, Name, To, Message, User_List)
          │ │ │      end.
          │ │ │  %%% If the user exists, send the message
          │ │ │ -server_transfer(From, Name, To, Message, User_List) ->
          │ │ │ +server_transfer(From, Name, To, Message, User_List) ->
          │ │ │      %% Find the receiver and send the message
          │ │ │ -    case lists:keysearch(To, 2, User_List) of
          │ │ │ +    case lists:keysearch(To, 2, User_List) of
          │ │ │          false ->
          │ │ │ -            From ! {messenger, receiver_not_found};
          │ │ │ -        {value, {ToPid, To}} ->
          │ │ │ -            ToPid ! {message_from, Name, Message},
          │ │ │ -            From ! {messenger, sent}
          │ │ │ +            From ! {messenger, receiver_not_found};
          │ │ │ +        {value, {ToPid, To}} ->
          │ │ │ +            ToPid ! {message_from, Name, Message},
          │ │ │ +            From ! {messenger, sent}
          │ │ │      end.
          │ │ │  
          │ │ │  
          │ │ │  %%% User Commands
          │ │ │ -logon(Name) ->
          │ │ │ -    case whereis(mess_client) of
          │ │ │ +logon(Name) ->
          │ │ │ +    case whereis(mess_client) of
          │ │ │          undefined ->
          │ │ │ -            register(mess_client,
          │ │ │ -                     spawn(messenger, client, [server_node(), Name]));
          │ │ │ +            register(mess_client,
          │ │ │ +                     spawn(messenger, client, [server_node(), Name]));
          │ │ │          _ -> already_logged_on
          │ │ │      end.
          │ │ │  
          │ │ │ -logoff() ->
          │ │ │ +logoff() ->
          │ │ │      mess_client ! logoff.
          │ │ │  
          │ │ │ -message(ToName, Message) ->
          │ │ │ -    case whereis(mess_client) of % Test if the client is running
          │ │ │ +message(ToName, Message) ->
          │ │ │ +    case whereis(mess_client) of % Test if the client is running
          │ │ │          undefined ->
          │ │ │              not_logged_on;
          │ │ │ -        _ -> mess_client ! {message_to, ToName, Message},
          │ │ │ +        _ -> mess_client ! {message_to, ToName, Message},
          │ │ │               ok
          │ │ │  end.
          │ │ │  
          │ │ │  
          │ │ │  %%% The client process which runs on each server node
          │ │ │ -client(Server_Node, Name) ->
          │ │ │ -    {messenger, Server_Node} ! {self(), logon, Name},
          │ │ │ -    await_result(),
          │ │ │ -    client(Server_Node).
          │ │ │ +client(Server_Node, Name) ->
          │ │ │ +    {messenger, Server_Node} ! {self(), logon, Name},
          │ │ │ +    await_result(),
          │ │ │ +    client(Server_Node).
          │ │ │  
          │ │ │ -client(Server_Node) ->
          │ │ │ +client(Server_Node) ->
          │ │ │      receive
          │ │ │          logoff ->
          │ │ │ -            {messenger, Server_Node} ! {self(), logoff},
          │ │ │ -            exit(normal);
          │ │ │ -        {message_to, ToName, Message} ->
          │ │ │ -            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
          │ │ │ -            await_result();
          │ │ │ -        {message_from, FromName, Message} ->
          │ │ │ -            io:format("Message from ~p: ~p~n", [FromName, Message])
          │ │ │ +            {messenger, Server_Node} ! {self(), logoff},
          │ │ │ +            exit(normal);
          │ │ │ +        {message_to, ToName, Message} ->
          │ │ │ +            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
          │ │ │ +            await_result();
          │ │ │ +        {message_from, FromName, Message} ->
          │ │ │ +            io:format("Message from ~p: ~p~n", [FromName, Message])
          │ │ │      end,
          │ │ │ -    client(Server_Node).
          │ │ │ +    client(Server_Node).
          │ │ │  
          │ │ │  %%% wait for a response from the server
          │ │ │ -await_result() ->
          │ │ │ +await_result() ->
          │ │ │      receive
          │ │ │ -        {messenger, stop, Why} -> % Stop the client
          │ │ │ -            io:format("~p~n", [Why]),
          │ │ │ -            exit(normal);
          │ │ │ -        {messenger, What} ->  % Normal response
          │ │ │ -            io:format("~p~n", [What])
          │ │ │ +        {messenger, stop, Why} -> % Stop the client
          │ │ │ +            io:format("~p~n", [Why]),
          │ │ │ +            exit(normal);
          │ │ │ +        {messenger, What} ->  % Normal response
          │ │ │ +            io:format("~p~n", [What])
          │ │ │      end.

          To use this program, you need to:

          • Configure the server_node() function.
          • Copy the compiled code (messenger.beam) to the directory on each computer │ │ │ where you start Erlang.

          In the following example using this program, nodes are started on four different │ │ │ computers. If you do not have that many machines available on your network, you │ │ │ can start several nodes on the same machine.

          Four Erlang nodes are started up: messenger@super, c1@bilbo, c2@kosken, │ │ │ -c3@gollum.

          First the server at messenger@super is started up:

          (messenger@super)1> messenger:start_server().
          │ │ │ -true

          Now Peter logs on at c1@bilbo:

          (c1@bilbo)1> messenger:logon(peter).
          │ │ │ +c3@gollum.

          First the server at messenger@super is started up:

          (messenger@super)1> messenger:start_server().
          │ │ │ +true

          Now Peter logs on at c1@bilbo:

          (c1@bilbo)1> messenger:logon(peter).
          │ │ │  true
          │ │ │ -logged_on

          James logs on at c2@kosken:

          (c2@kosken)1> messenger:logon(james).
          │ │ │ +logged_on

          James logs on at c2@kosken:

          (c2@kosken)1> messenger:logon(james).
          │ │ │  true
          │ │ │ -logged_on

          And Fred logs on at c3@gollum:

          (c3@gollum)1> messenger:logon(fred).
          │ │ │ +logged_on

          And Fred logs on at c3@gollum:

          (c3@gollum)1> messenger:logon(fred).
          │ │ │  true
          │ │ │ -logged_on

          Now Peter sends Fred a message:

          (c1@bilbo)2> messenger:message(fred, "hello").
          │ │ │ +logged_on

          Now Peter sends Fred a message:

          (c1@bilbo)2> messenger:message(fred, "hello").
          │ │ │  ok
          │ │ │  sent

          Fred receives the message and sends a message to Peter and logs off:

          Message from peter: "hello"
          │ │ │ -(c3@gollum)2> messenger:message(peter, "go away, I'm busy").
          │ │ │ +(c3@gollum)2> messenger:message(peter, "go away, I'm busy").
          │ │ │  ok
          │ │ │  sent
          │ │ │ -(c3@gollum)3> messenger:logoff().
          │ │ │ -logoff

          James now tries to send a message to Fred:

          (c2@kosken)2> messenger:message(fred, "peter doesn't like you").
          │ │ │ +(c3@gollum)3> messenger:logoff().
          │ │ │ +logoff

          James now tries to send a message to Fred:

          (c2@kosken)2> messenger:message(fred, "peter doesn't like you").
          │ │ │  ok
          │ │ │  receiver_not_found

          But this fails as Fred has already logged off.

          First let us look at some of the new concepts that have been introduced.

          There are two versions of the server_transfer function: one with four │ │ │ arguments (server_transfer/4) and one with five (server_transfer/5). These │ │ │ are regarded by Erlang as two separate functions.

          Notice how to write the server function so that it calls itself, through │ │ │ server(User_List), and thus creates a loop. The Erlang compiler is "clever" │ │ │ and optimizes the code so that this really is a sort of loop and not a proper │ │ │ function call. But this only works if there is no code after the call. │ │ │ Otherwise, the compiler expects the call to return and make a proper function │ │ │ call. This would result in the process getting bigger and bigger for every loop.

          Functions in the lists module are used. This is a very useful module and a │ │ │ study of the manual page is recommended (erl -man lists). │ │ │ lists:keymember(Key,Position,Lists) looks through a list of tuples and looks │ │ │ at Position in each tuple to see if it is the same as Key. The first element │ │ │ is position 1. If it finds a tuple where the element at Position is the same │ │ │ -as Key, it returns true, otherwise false.

          3> lists:keymember(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
          │ │ │ +as Key, it returns true, otherwise false.

          3> lists:keymember(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
          │ │ │  true
          │ │ │ -4> lists:keymember(p, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
          │ │ │ +4> lists:keymember(p, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
          │ │ │  false

          lists:keydelete works in the same way but deletes the first tuple found (if │ │ │ -any) and returns the remaining list:

          5> lists:keydelete(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
          │ │ │ -[{x,y,z},{b,b,b},{q,r,s}]

          lists:keysearch is like lists:keymember, but it returns │ │ │ +any) and returns the remaining list:

          5> lists:keydelete(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
          │ │ │ +[{x,y,z},{b,b,b},{q,r,s}]

          lists:keysearch is like lists:keymember, but it returns │ │ │ {value,Tuple_Found} or the atom false.

          There are many very useful functions in the lists module.

          An Erlang process (conceptually) runs until it does a receive and there is no │ │ │ message which it wants to receive in the message queue. "conceptually" is used │ │ │ here because the Erlang system shares the CPU time between the active processes │ │ │ in the system.

          A process terminates when there is nothing more for it to do, that is, the last │ │ │ function it calls simply returns and does not call another function. Another way │ │ │ for a process to terminate is for it to call exit/1. The argument │ │ │ to exit/1 has a special meaning, which is discussed later. In this │ │ │ example, exit(normal) is done, which has the same effect as a │ │ │ process running out of functions to call.

          The BIF whereis(RegisteredName) checks if a registered process │ │ │ of name RegisteredName exists. If it exists, the pid of that process is │ │ │ returned. If it does not exist, the atom undefined is returned.

          You should by now be able to understand most of the code in the │ │ │ messenger-module. Let us study one case in detail: a message is sent from one │ │ │ -user to another.

          The first user "sends" the message in the example above by:

          messenger:message(fred, "hello")

          After testing that the client process exists:

          whereis(mess_client)

          And a message is sent to mess_client:

          mess_client ! {message_to, fred, "hello"}

          The client sends the message to the server by:

          {messenger, messenger@super} ! {self(), message_to, fred, "hello"},

          And waits for a reply from the server.

          The server receives this message and calls:

          server_transfer(From, fred, "hello", User_List),

          This checks that the pid From is in the User_List:

          lists:keysearch(From, 1, User_List)

          If keysearch returns the atom false, some error has occurred and the server │ │ │ -sends back the message:

          From ! {messenger, stop, you_are_not_logged_on}

          This is received by the client, which in turn does exit(normal) │ │ │ +user to another.

          The first user "sends" the message in the example above by:

          messenger:message(fred, "hello")

          After testing that the client process exists:

          whereis(mess_client)

          And a message is sent to mess_client:

          mess_client ! {message_to, fred, "hello"}

          The client sends the message to the server by:

          {messenger, messenger@super} ! {self(), message_to, fred, "hello"},

          And waits for a reply from the server.

          The server receives this message and calls:

          server_transfer(From, fred, "hello", User_List),

          This checks that the pid From is in the User_List:

          lists:keysearch(From, 1, User_List)

          If keysearch returns the atom false, some error has occurred and the server │ │ │ +sends back the message:

          From ! {messenger, stop, you_are_not_logged_on}

          This is received by the client, which in turn does exit(normal) │ │ │ and terminates. If keysearch returns {value,{From,Name}} it is certain that │ │ │ -the user is logged on and that his name (peter) is in variable Name.

          Let us now call:

          server_transfer(From, peter, fred, "hello", User_List)

          Notice that as this is server_transfer/5, it is not the same as the previous │ │ │ +the user is logged on and that his name (peter) is in variable Name.

          Let us now call:

          server_transfer(From, peter, fred, "hello", User_List)

          Notice that as this is server_transfer/5, it is not the same as the previous │ │ │ function server_transfer/4. Another keysearch is done on User_List to find │ │ │ -the pid of the client corresponding to fred:

          lists:keysearch(fred, 2, User_List)

          This time argument 2 is used, which is the second element in the tuple. If this │ │ │ +the pid of the client corresponding to fred:

          lists:keysearch(fred, 2, User_List)

          This time argument 2 is used, which is the second element in the tuple. If this │ │ │ returns the atom false, fred is not logged on and the following message is │ │ │ -sent:

          From ! {messenger, receiver_not_found};

          This is received by the client.

          If keysearch returns:

          {value, {ToPid, fred}}

          The following message is sent to fred's client:

          ToPid ! {message_from, peter, "hello"},

          The following message is sent to peter's client:

          From ! {messenger, sent}

          Fred's client receives the message and prints it:

          {message_from, peter, "hello"} ->
          │ │ │ -    io:format("Message from ~p: ~p~n", [peter, "hello"])

          Peter's client receives the message in the await_result function.

          │ │ │ +sent:

          From ! {messenger, receiver_not_found};

          This is received by the client.

          If keysearch returns:

          {value, {ToPid, fred}}

          The following message is sent to fred's client:

          ToPid ! {message_from, peter, "hello"},

          The following message is sent to peter's client:

          From ! {messenger, sent}

          Fred's client receives the message and prints it:

          {message_from, peter, "hello"} ->
          │ │ │ +    io:format("Message from ~p: ~p~n", [peter, "hello"])

          Peter's client receives the message in the await_result function.

          │ │ │
          │ │ │ │ │ │
          │ │ │
          │ │ │ │ │ │

          rel(4) manual page in │ │ │ SASL), which specifies the ERTS version and lists all applications that are to │ │ │ be included in the new basic target system. An example is the following │ │ │ mysystem.rel file:

          %% mysystem.rel
          │ │ │ -{release,
          │ │ │ - {"MYSYSTEM", "FIRST"},
          │ │ │ - {erts, "5.10.4"},
          │ │ │ - [{kernel, "2.16.4"},
          │ │ │ -  {stdlib, "1.19.4"},
          │ │ │ -  {sasl, "2.3.4"},
          │ │ │ -  {pea, "1.0"}]}.

          The listed applications are not only original Erlang/OTP applications but │ │ │ +{release, │ │ │ + {"MYSYSTEM", "FIRST"}, │ │ │ + {erts, "5.10.4"}, │ │ │ + [{kernel, "2.16.4"}, │ │ │ + {stdlib, "1.19.4"}, │ │ │ + {sasl, "2.3.4"}, │ │ │ + {pea, "1.0"}]}.

          The listed applications are not only original Erlang/OTP applications but │ │ │ possibly also new applications that you have written (here exemplified by the │ │ │ application Pea (pea)).

          Step 2. Start Erlang/OTP from the directory where the mysystem.rel file │ │ │ resides:

          % erl -pa /home/user/target_system/myapps/pea-1.0/ebin

          The -pa argument prepends the path to the ebin directory for │ │ │ the Pea application to the code path.

          Step 3. Create the target system:

          1> target_system:create("mysystem").

          The function target_system:create/1 performs the following:

          1. Reads the file mysystem.rel and creates a new file plain.rel. │ │ │ The new file is identical to the original, except that it only │ │ │ lists the Kernel and STDLIB applications.

          2. From the files mysystem.rel and plain.rel creates the files │ │ │ mysystem.script, mysystem.boot, plain.script, and plain.boot │ │ │ @@ -242,25 +242,25 @@ │ │ │ │ │ │ │ │ │ │ │ │ Creating the Next Version │ │ │ │ │ │

            In this example the Pea application has been changed, and so are the │ │ │ applications ERTS, Kernel, STDLIB and SASL.

            Step 1. Create the file .rel:

            %% mysystem2.rel
            │ │ │ -{release,
            │ │ │ - {"MYSYSTEM", "SECOND"},
            │ │ │ - {erts, "6.0"},
            │ │ │ - [{kernel, "3.0"},
            │ │ │ -  {stdlib, "2.0"},
            │ │ │ -  {sasl, "2.4"},
            │ │ │ -  {pea, "2.0"}]}.

            Step 2. Create the application upgrade file (see │ │ │ +{release, │ │ │ + {"MYSYSTEM", "SECOND"}, │ │ │ + {erts, "6.0"}, │ │ │ + [{kernel, "3.0"}, │ │ │ + {stdlib, "2.0"}, │ │ │ + {sasl, "2.4"}, │ │ │ + {pea, "2.0"}]}.

          Step 2. Create the application upgrade file (see │ │ │ appup in SASL) for Pea, for example:

          %% pea.appup
          │ │ │ -{"2.0",
          │ │ │ - [{"1.0",[{load_module,pea_lib}]}],
          │ │ │ - [{"1.0",[{load_module,pea_lib}]}]}.

          Step 3. From the directory where the file mysystem2.rel resides, start the │ │ │ +{"2.0", │ │ │ + [{"1.0",[{load_module,pea_lib}]}], │ │ │ + [{"1.0",[{load_module,pea_lib}]}]}.

      Step 3. From the directory where the file mysystem2.rel resides, start the │ │ │ Erlang/OTP system, giving the path to the new version of Pea:

      % erl -pa /home/user/target_system/myapps/pea-2.0/ebin

      Step 4. Create the release upgrade file (see relup │ │ │ in SASL):

      1> systools:make_relup("mysystem2",["mysystem"],["mysystem"],
      │ │ │      [{path,["/home/user/target_system/myapps/pea-1.0/ebin",
      │ │ │      "/my/old/erlang/lib/*/ebin"]}]).

      Here "mysystem" is the base release and "mysystem2" is the release to │ │ │ upgrade to.

      The path option is used for pointing out the old version of all applications. │ │ │ (The new versions are already in the code path - assuming of course that the │ │ │ Erlang node on which this is executed is running the correct version of │ │ │ @@ -292,21 +292,21 @@ │ │ │ {continue_after_restart,"FIRST",[]} │ │ │ heart: Tue Apr 1 12:15:10 2014: Erlang has closed. │ │ │ heart: Tue Apr 1 12:15:11 2014: Executed "/usr/local/erl-target/bin/start /usr/local/erl-target/releases/new_start_erl.data" -> 0. Terminating. │ │ │ [End]

      The above return value and output after the call to │ │ │ release_handler:install_release/1 means that the release_handler has │ │ │ restarted the node by using heart. This is always done when the upgrade │ │ │ involves a change of the applications ERTS, Kernel, STDLIB, or SASL. For more │ │ │ -information, see Upgrade when Erlang/OTP has Changed.

      The node is accessible through a new pipe:

      % /usr/local/erl-target/bin/to_erl /tmp/erlang.pipe.2

      List the available releases in the system:

      1> release_handler:which_releases().
      │ │ │ -[{"MYSYSTEM","SECOND",
      │ │ │ -  ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"],
      │ │ │ -  current},
      │ │ │ - {"MYSYSTEM","FIRST",
      │ │ │ -  ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"],
      │ │ │ -  permanent}]

      Our new release, "SECOND", is now the current release, but we can also see that │ │ │ +information, see Upgrade when Erlang/OTP has Changed.

      The node is accessible through a new pipe:

      % /usr/local/erl-target/bin/to_erl /tmp/erlang.pipe.2

      List the available releases in the system:

      1> release_handler:which_releases().
      │ │ │ +[{"MYSYSTEM","SECOND",
      │ │ │ +  ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"],
      │ │ │ +  current},
      │ │ │ + {"MYSYSTEM","FIRST",
      │ │ │ +  ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"],
      │ │ │ +  permanent}]

      Our new release, "SECOND", is now the current release, but we can also see that │ │ │ our "FIRST" release is still permanent. This means that if the node would be │ │ │ restarted now, it would come up running the "FIRST" release again.

      Step 3. Make the new release permanent:

      2> release_handler:make_permanent("SECOND").

      Check the releases again:

      3> release_handler:which_releases().
      │ │ │  [{"MYSYSTEM","SECOND",
      │ │ │    ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"],
      │ │ │    permanent},
      │ │ │   {"MYSYSTEM","FIRST",
      │ │ │    ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"],
      │ │ │ @@ -315,268 +315,268 @@
      │ │ │    
      │ │ │      
      │ │ │    
      │ │ │    Listing of target_system.erl
      │ │ │  
      │ │ │  

      This module can also be found in the examples directory of the SASL │ │ │ application.

      
      │ │ │ --module(target_system).
      │ │ │ --export([create/1, create/2, install/2]).
      │ │ │ +-module(target_system).
      │ │ │ +-export([create/1, create/2, install/2]).
      │ │ │  
      │ │ │  %% Note: RelFileName below is the *stem* without trailing .rel,
      │ │ │  %% .script etc.
      │ │ │  %%
      │ │ │  
      │ │ │  %% create(RelFileName)
      │ │ │  %%
      │ │ │ -create(RelFileName) ->
      │ │ │ -    create(RelFileName,[]).
      │ │ │ +create(RelFileName) ->
      │ │ │ +    create(RelFileName,[]).
      │ │ │  
      │ │ │ -create(RelFileName,SystoolsOpts) ->
      │ │ │ +create(RelFileName,SystoolsOpts) ->
      │ │ │      RelFile = RelFileName ++ ".rel",
      │ │ │ -    Dir = filename:dirname(RelFileName),
      │ │ │ -    PlainRelFileName = filename:join(Dir,"plain"),
      │ │ │ +    Dir = filename:dirname(RelFileName),
      │ │ │ +    PlainRelFileName = filename:join(Dir,"plain"),
      │ │ │      PlainRelFile = PlainRelFileName ++ ".rel",
      │ │ │ -    io:fwrite("Reading file: ~ts ...~n", [RelFile]),
      │ │ │ -    {ok, [RelSpec]} = file:consult(RelFile),
      │ │ │ -    io:fwrite("Creating file: ~ts from ~ts ...~n",
      │ │ │ -              [PlainRelFile, RelFile]),
      │ │ │ -    {release,
      │ │ │ -     {RelName, RelVsn},
      │ │ │ -     {erts, ErtsVsn},
      │ │ │ -     AppVsns} = RelSpec,
      │ │ │ -    PlainRelSpec = {release,
      │ │ │ -                    {RelName, RelVsn},
      │ │ │ -                    {erts, ErtsVsn},
      │ │ │ -                    lists:filter(fun({kernel, _}) ->
      │ │ │ +    io:fwrite("Reading file: ~ts ...~n", [RelFile]),
      │ │ │ +    {ok, [RelSpec]} = file:consult(RelFile),
      │ │ │ +    io:fwrite("Creating file: ~ts from ~ts ...~n",
      │ │ │ +              [PlainRelFile, RelFile]),
      │ │ │ +    {release,
      │ │ │ +     {RelName, RelVsn},
      │ │ │ +     {erts, ErtsVsn},
      │ │ │ +     AppVsns} = RelSpec,
      │ │ │ +    PlainRelSpec = {release,
      │ │ │ +                    {RelName, RelVsn},
      │ │ │ +                    {erts, ErtsVsn},
      │ │ │ +                    lists:filter(fun({kernel, _}) ->
      │ │ │                                           true;
      │ │ │ -                                    ({stdlib, _}) ->
      │ │ │ +                                    ({stdlib, _}) ->
      │ │ │                                           true;
      │ │ │ -                                    (_) ->
      │ │ │ +                                    (_) ->
      │ │ │                                           false
      │ │ │ -                                 end, AppVsns)
      │ │ │ -                   },
      │ │ │ -    {ok, Fd} = file:open(PlainRelFile, [write]),
      │ │ │ -    io:fwrite(Fd, "~p.~n", [PlainRelSpec]),
      │ │ │ -    file:close(Fd),
      │ │ │ -
      │ │ │ -    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
      │ │ │ -	      [PlainRelFileName,PlainRelFileName]),
      │ │ │ -    make_script(PlainRelFileName,SystoolsOpts),
      │ │ │ -
      │ │ │ -    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
      │ │ │ -              [RelFileName, RelFileName]),
      │ │ │ -    make_script(RelFileName,SystoolsOpts),
      │ │ │ +                                 end, AppVsns)
      │ │ │ +                   },
      │ │ │ +    {ok, Fd} = file:open(PlainRelFile, [write]),
      │ │ │ +    io:fwrite(Fd, "~p.~n", [PlainRelSpec]),
      │ │ │ +    file:close(Fd),
      │ │ │ +
      │ │ │ +    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
      │ │ │ +	      [PlainRelFileName,PlainRelFileName]),
      │ │ │ +    make_script(PlainRelFileName,SystoolsOpts),
      │ │ │ +
      │ │ │ +    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
      │ │ │ +              [RelFileName, RelFileName]),
      │ │ │ +    make_script(RelFileName,SystoolsOpts),
      │ │ │  
      │ │ │      TarFileName = RelFileName ++ ".tar.gz",
      │ │ │ -    io:fwrite("Creating tar file ~ts ...~n", [TarFileName]),
      │ │ │ -    make_tar(RelFileName,SystoolsOpts),
      │ │ │ +    io:fwrite("Creating tar file ~ts ...~n", [TarFileName]),
      │ │ │ +    make_tar(RelFileName,SystoolsOpts),
      │ │ │  
      │ │ │ -    TmpDir = filename:join(Dir,"tmp"),
      │ │ │ -    io:fwrite("Creating directory ~tp ...~n",[TmpDir]),
      │ │ │ -    file:make_dir(TmpDir),
      │ │ │ -
      │ │ │ -    io:fwrite("Extracting ~ts into directory ~ts ...~n", [TarFileName,TmpDir]),
      │ │ │ -    extract_tar(TarFileName, TmpDir),
      │ │ │ -
      │ │ │ -    TmpBinDir = filename:join([TmpDir, "bin"]),
      │ │ │ -    ErtsBinDir = filename:join([TmpDir, "erts-" ++ ErtsVsn, "bin"]),
      │ │ │ -    io:fwrite("Deleting \"erl\" and \"start\" in directory ~ts ...~n",
      │ │ │ -              [ErtsBinDir]),
      │ │ │ -    file:delete(filename:join([ErtsBinDir, "erl"])),
      │ │ │ -    file:delete(filename:join([ErtsBinDir, "start"])),
      │ │ │ -
      │ │ │ -    io:fwrite("Creating temporary directory ~ts ...~n", [TmpBinDir]),
      │ │ │ -    file:make_dir(TmpBinDir),
      │ │ │ -
      │ │ │ -    io:fwrite("Copying file \"~ts.boot\" to ~ts ...~n",
      │ │ │ -              [PlainRelFileName, filename:join([TmpBinDir, "start.boot"])]),
      │ │ │ -    copy_file(PlainRelFileName++".boot",filename:join([TmpBinDir, "start.boot"])),
      │ │ │ +    TmpDir = filename:join(Dir,"tmp"),
      │ │ │ +    io:fwrite("Creating directory ~tp ...~n",[TmpDir]),
      │ │ │ +    file:make_dir(TmpDir),
      │ │ │ +
      │ │ │ +    io:fwrite("Extracting ~ts into directory ~ts ...~n", [TarFileName,TmpDir]),
      │ │ │ +    extract_tar(TarFileName, TmpDir),
      │ │ │ +
      │ │ │ +    TmpBinDir = filename:join([TmpDir, "bin"]),
      │ │ │ +    ErtsBinDir = filename:join([TmpDir, "erts-" ++ ErtsVsn, "bin"]),
      │ │ │ +    io:fwrite("Deleting \"erl\" and \"start\" in directory ~ts ...~n",
      │ │ │ +              [ErtsBinDir]),
      │ │ │ +    file:delete(filename:join([ErtsBinDir, "erl"])),
      │ │ │ +    file:delete(filename:join([ErtsBinDir, "start"])),
      │ │ │ +
      │ │ │ +    io:fwrite("Creating temporary directory ~ts ...~n", [TmpBinDir]),
      │ │ │ +    file:make_dir(TmpBinDir),
      │ │ │ +
      │ │ │ +    io:fwrite("Copying file \"~ts.boot\" to ~ts ...~n",
      │ │ │ +              [PlainRelFileName, filename:join([TmpBinDir, "start.boot"])]),
      │ │ │ +    copy_file(PlainRelFileName++".boot",filename:join([TmpBinDir, "start.boot"])),
      │ │ │  
      │ │ │ -    io:fwrite("Copying files \"epmd\", \"run_erl\" and \"to_erl\" from \n"
      │ │ │ +    io:fwrite("Copying files \"epmd\", \"run_erl\" and \"to_erl\" from \n"
      │ │ │                "~ts to ~ts ...~n",
      │ │ │ -              [ErtsBinDir, TmpBinDir]),
      │ │ │ -    copy_file(filename:join([ErtsBinDir, "epmd"]),
      │ │ │ -              filename:join([TmpBinDir, "epmd"]), [preserve]),
      │ │ │ -    copy_file(filename:join([ErtsBinDir, "run_erl"]),
      │ │ │ -              filename:join([TmpBinDir, "run_erl"]), [preserve]),
      │ │ │ -    copy_file(filename:join([ErtsBinDir, "to_erl"]),
      │ │ │ -              filename:join([TmpBinDir, "to_erl"]), [preserve]),
      │ │ │ +              [ErtsBinDir, TmpBinDir]),
      │ │ │ +    copy_file(filename:join([ErtsBinDir, "epmd"]),
      │ │ │ +              filename:join([TmpBinDir, "epmd"]), [preserve]),
      │ │ │ +    copy_file(filename:join([ErtsBinDir, "run_erl"]),
      │ │ │ +              filename:join([TmpBinDir, "run_erl"]), [preserve]),
      │ │ │ +    copy_file(filename:join([ErtsBinDir, "to_erl"]),
      │ │ │ +              filename:join([TmpBinDir, "to_erl"]), [preserve]),
      │ │ │  
      │ │ │      %% This is needed if 'start' script created from 'start.src' shall
      │ │ │      %% be used as it points out this directory as log dir for 'run_erl'
      │ │ │ -    TmpLogDir = filename:join([TmpDir, "log"]),
      │ │ │ -    io:fwrite("Creating temporary directory ~ts ...~n", [TmpLogDir]),
      │ │ │ -    ok = file:make_dir(TmpLogDir),
      │ │ │ -
      │ │ │ -    StartErlDataFile = filename:join([TmpDir, "releases", "start_erl.data"]),
      │ │ │ -    io:fwrite("Creating ~ts ...~n", [StartErlDataFile]),
      │ │ │ -    StartErlData = io_lib:fwrite("~s ~s~n", [ErtsVsn, RelVsn]),
      │ │ │ -    write_file(StartErlDataFile, StartErlData),
      │ │ │ -
      │ │ │ -    io:fwrite("Recreating tar file ~ts from contents in directory ~ts ...~n",
      │ │ │ -	      [TarFileName,TmpDir]),
      │ │ │ -    {ok, Tar} = erl_tar:open(TarFileName, [write, compressed]),
      │ │ │ +    TmpLogDir = filename:join([TmpDir, "log"]),
      │ │ │ +    io:fwrite("Creating temporary directory ~ts ...~n", [TmpLogDir]),
      │ │ │ +    ok = file:make_dir(TmpLogDir),
      │ │ │ +
      │ │ │ +    StartErlDataFile = filename:join([TmpDir, "releases", "start_erl.data"]),
      │ │ │ +    io:fwrite("Creating ~ts ...~n", [StartErlDataFile]),
      │ │ │ +    StartErlData = io_lib:fwrite("~s ~s~n", [ErtsVsn, RelVsn]),
      │ │ │ +    write_file(StartErlDataFile, StartErlData),
      │ │ │ +
      │ │ │ +    io:fwrite("Recreating tar file ~ts from contents in directory ~ts ...~n",
      │ │ │ +	      [TarFileName,TmpDir]),
      │ │ │ +    {ok, Tar} = erl_tar:open(TarFileName, [write, compressed]),
      │ │ │      %% {ok, Cwd} = file:get_cwd(),
      │ │ │      %% file:set_cwd("tmp"),
      │ │ │      ErtsDir = "erts-"++ErtsVsn,
      │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"bin"), "bin", []),
      │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,ErtsDir), ErtsDir, []),
      │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"releases"), "releases", []),
      │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"lib"), "lib", []),
      │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"log"), "log", []),
      │ │ │ -    erl_tar:close(Tar),
      │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"bin"), "bin", []),
      │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,ErtsDir), ErtsDir, []),
      │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"releases"), "releases", []),
      │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"lib"), "lib", []),
      │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"log"), "log", []),
      │ │ │ +    erl_tar:close(Tar),
      │ │ │      %% file:set_cwd(Cwd),
      │ │ │ -    io:fwrite("Removing directory ~ts ...~n",[TmpDir]),
      │ │ │ -    remove_dir_tree(TmpDir),
      │ │ │ +    io:fwrite("Removing directory ~ts ...~n",[TmpDir]),
      │ │ │ +    remove_dir_tree(TmpDir),
      │ │ │      ok.
      │ │ │  
      │ │ │  
      │ │ │ -install(RelFileName, RootDir) ->
      │ │ │ +install(RelFileName, RootDir) ->
      │ │ │      TarFile = RelFileName ++ ".tar.gz",
      │ │ │ -    io:fwrite("Extracting ~ts ...~n", [TarFile]),
      │ │ │ -    extract_tar(TarFile, RootDir),
      │ │ │ -    StartErlDataFile = filename:join([RootDir, "releases", "start_erl.data"]),
      │ │ │ -    {ok, StartErlData} = read_txt_file(StartErlDataFile),
      │ │ │ -    [ErlVsn, _RelVsn| _] = string:tokens(StartErlData, " \n"),
      │ │ │ -    ErtsBinDir = filename:join([RootDir, "erts-" ++ ErlVsn, "bin"]),
      │ │ │ -    BinDir = filename:join([RootDir, "bin"]),
      │ │ │ -    io:fwrite("Substituting in erl.src, start.src and start_erl.src to "
      │ │ │ -              "form erl, start and start_erl ...\n"),
      │ │ │ -    subst_src_scripts(["erl", "start", "start_erl"], ErtsBinDir, BinDir,
      │ │ │ -                      [{"FINAL_ROOTDIR", RootDir}, {"EMU", "beam"}],
      │ │ │ -                      [preserve]),
      │ │ │ +    io:fwrite("Extracting ~ts ...~n", [TarFile]),
      │ │ │ +    extract_tar(TarFile, RootDir),
      │ │ │ +    StartErlDataFile = filename:join([RootDir, "releases", "start_erl.data"]),
      │ │ │ +    {ok, StartErlData} = read_txt_file(StartErlDataFile),
      │ │ │ +    [ErlVsn, _RelVsn| _] = string:tokens(StartErlData, " \n"),
      │ │ │ +    ErtsBinDir = filename:join([RootDir, "erts-" ++ ErlVsn, "bin"]),
      │ │ │ +    BinDir = filename:join([RootDir, "bin"]),
      │ │ │ +    io:fwrite("Substituting in erl.src, start.src and start_erl.src to "
      │ │ │ +              "form erl, start and start_erl ...\n"),
      │ │ │ +    subst_src_scripts(["erl", "start", "start_erl"], ErtsBinDir, BinDir,
      │ │ │ +                      [{"FINAL_ROOTDIR", RootDir}, {"EMU", "beam"}],
      │ │ │ +                      [preserve]),
      │ │ │      %%! Workaround for pre OTP 17.0: start.src and start_erl.src did
      │ │ │      %%! not have correct permissions, so the above 'preserve' option did not help
      │ │ │ -    ok = file:change_mode(filename:join(BinDir,"start"),8#0755),
      │ │ │ -    ok = file:change_mode(filename:join(BinDir,"start_erl"),8#0755),
      │ │ │ +    ok = file:change_mode(filename:join(BinDir,"start"),8#0755),
      │ │ │ +    ok = file:change_mode(filename:join(BinDir,"start_erl"),8#0755),
      │ │ │  
      │ │ │ -    io:fwrite("Creating the RELEASES file ...\n"),
      │ │ │ -    create_RELEASES(RootDir, filename:join([RootDir, "releases",
      │ │ │ -					    filename:basename(RelFileName)])).
      │ │ │ +    io:fwrite("Creating the RELEASES file ...\n"),
      │ │ │ +    create_RELEASES(RootDir, filename:join([RootDir, "releases",
      │ │ │ +					    filename:basename(RelFileName)])).
      │ │ │  
      │ │ │  %% LOCALS
      │ │ │  
      │ │ │  %% make_script(RelFileName,Opts)
      │ │ │  %%
      │ │ │ -make_script(RelFileName,Opts) ->
      │ │ │ -    systools:make_script(RelFileName, [no_module_tests,
      │ │ │ -				       {outdir,filename:dirname(RelFileName)}
      │ │ │ -				       |Opts]).
      │ │ │ +make_script(RelFileName,Opts) ->
      │ │ │ +    systools:make_script(RelFileName, [no_module_tests,
      │ │ │ +				       {outdir,filename:dirname(RelFileName)}
      │ │ │ +				       |Opts]).
      │ │ │  
      │ │ │  %% make_tar(RelFileName,Opts)
      │ │ │  %%
      │ │ │ -make_tar(RelFileName,Opts) ->
      │ │ │ -    RootDir = code:root_dir(),
      │ │ │ -    systools:make_tar(RelFileName, [{erts, RootDir},
      │ │ │ -				    {outdir,filename:dirname(RelFileName)}
      │ │ │ -				    |Opts]).
      │ │ │ +make_tar(RelFileName,Opts) ->
      │ │ │ +    RootDir = code:root_dir(),
      │ │ │ +    systools:make_tar(RelFileName, [{erts, RootDir},
      │ │ │ +				    {outdir,filename:dirname(RelFileName)}
      │ │ │ +				    |Opts]).
      │ │ │  
      │ │ │  %% extract_tar(TarFile, DestDir)
      │ │ │  %%
      │ │ │ -extract_tar(TarFile, DestDir) ->
      │ │ │ -    erl_tar:extract(TarFile, [{cwd, DestDir}, compressed]).
      │ │ │ +extract_tar(TarFile, DestDir) ->
      │ │ │ +    erl_tar:extract(TarFile, [{cwd, DestDir}, compressed]).
      │ │ │  
      │ │ │ -create_RELEASES(DestDir, RelFileName) ->
      │ │ │ -    release_handler:create_RELEASES(DestDir, RelFileName ++ ".rel").
      │ │ │ +create_RELEASES(DestDir, RelFileName) ->
      │ │ │ +    release_handler:create_RELEASES(DestDir, RelFileName ++ ".rel").
      │ │ │  
      │ │ │ -subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) ->
      │ │ │ -    lists:foreach(fun(Script) ->
      │ │ │ -                          subst_src_script(Script, SrcDir, DestDir,
      │ │ │ -                                           Vars, Opts)
      │ │ │ -                  end, Scripts).
      │ │ │ -
      │ │ │ -subst_src_script(Script, SrcDir, DestDir, Vars, Opts) ->
      │ │ │ -    subst_file(filename:join([SrcDir, Script ++ ".src"]),
      │ │ │ -               filename:join([DestDir, Script]),
      │ │ │ -               Vars, Opts).
      │ │ │ -
      │ │ │ -subst_file(Src, Dest, Vars, Opts) ->
      │ │ │ -    {ok, Conts} = read_txt_file(Src),
      │ │ │ -    NConts = subst(Conts, Vars),
      │ │ │ -    write_file(Dest, NConts),
      │ │ │ -    case lists:member(preserve, Opts) of
      │ │ │ +subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) ->
      │ │ │ +    lists:foreach(fun(Script) ->
      │ │ │ +                          subst_src_script(Script, SrcDir, DestDir,
      │ │ │ +                                           Vars, Opts)
      │ │ │ +                  end, Scripts).
      │ │ │ +
      │ │ │ +subst_src_script(Script, SrcDir, DestDir, Vars, Opts) ->
      │ │ │ +    subst_file(filename:join([SrcDir, Script ++ ".src"]),
      │ │ │ +               filename:join([DestDir, Script]),
      │ │ │ +               Vars, Opts).
      │ │ │ +
      │ │ │ +subst_file(Src, Dest, Vars, Opts) ->
      │ │ │ +    {ok, Conts} = read_txt_file(Src),
      │ │ │ +    NConts = subst(Conts, Vars),
      │ │ │ +    write_file(Dest, NConts),
      │ │ │ +    case lists:member(preserve, Opts) of
      │ │ │          true ->
      │ │ │ -            {ok, FileInfo} = file:read_file_info(Src),
      │ │ │ -            file:write_file_info(Dest, FileInfo);
      │ │ │ +            {ok, FileInfo} = file:read_file_info(Src),
      │ │ │ +            file:write_file_info(Dest, FileInfo);
      │ │ │          false ->
      │ │ │              ok
      │ │ │      end.
      │ │ │  
      │ │ │  %% subst(Str, Vars)
      │ │ │  %% Vars = [{Var, Val}]
      │ │ │  %% Var = Val = string()
      │ │ │  %% Substitute all occurrences of %Var% for Val in Str, using the list
      │ │ │  %% of variables in Vars.
      │ │ │  %%
      │ │ │ -subst(Str, Vars) ->
      │ │ │ -    subst(Str, Vars, []).
      │ │ │ +subst(Str, Vars) ->
      │ │ │ +    subst(Str, Vars, []).
      │ │ │  
      │ │ │ -subst([$%, C| Rest], Vars, Result) when $A =< C, C =< $Z ->
      │ │ │ -    subst_var([C| Rest], Vars, Result, []);
      │ │ │ -subst([$%, C| Rest], Vars, Result) when $a =< C, C =< $z ->
      │ │ │ -    subst_var([C| Rest], Vars, Result, []);
      │ │ │ -subst([$%, C| Rest], Vars, Result) when  C == $_ ->
      │ │ │ -    subst_var([C| Rest], Vars, Result, []);
      │ │ │ -subst([C| Rest], Vars, Result) ->
      │ │ │ -    subst(Rest, Vars, [C| Result]);
      │ │ │ -subst([], _Vars, Result) ->
      │ │ │ -    lists:reverse(Result).
      │ │ │ -
      │ │ │ -subst_var([$%| Rest], Vars, Result, VarAcc) ->
      │ │ │ -    Key = lists:reverse(VarAcc),
      │ │ │ -    case lists:keysearch(Key, 1, Vars) of
      │ │ │ -        {value, {Key, Value}} ->
      │ │ │ -            subst(Rest, Vars, lists:reverse(Value, Result));
      │ │ │ +subst([$%, C| Rest], Vars, Result) when $A =< C, C =< $Z ->
      │ │ │ +    subst_var([C| Rest], Vars, Result, []);
      │ │ │ +subst([$%, C| Rest], Vars, Result) when $a =< C, C =< $z ->
      │ │ │ +    subst_var([C| Rest], Vars, Result, []);
      │ │ │ +subst([$%, C| Rest], Vars, Result) when  C == $_ ->
      │ │ │ +    subst_var([C| Rest], Vars, Result, []);
      │ │ │ +subst([C| Rest], Vars, Result) ->
      │ │ │ +    subst(Rest, Vars, [C| Result]);
      │ │ │ +subst([], _Vars, Result) ->
      │ │ │ +    lists:reverse(Result).
      │ │ │ +
      │ │ │ +subst_var([$%| Rest], Vars, Result, VarAcc) ->
      │ │ │ +    Key = lists:reverse(VarAcc),
      │ │ │ +    case lists:keysearch(Key, 1, Vars) of
      │ │ │ +        {value, {Key, Value}} ->
      │ │ │ +            subst(Rest, Vars, lists:reverse(Value, Result));
      │ │ │          false ->
      │ │ │ -            subst(Rest, Vars, [$%| VarAcc ++ [$%| Result]])
      │ │ │ +            subst(Rest, Vars, [$%| VarAcc ++ [$%| Result]])
      │ │ │      end;
      │ │ │ -subst_var([C| Rest], Vars, Result, VarAcc) ->
      │ │ │ -    subst_var(Rest, Vars, Result, [C| VarAcc]);
      │ │ │ -subst_var([], Vars, Result, VarAcc) ->
      │ │ │ -    subst([], Vars, [VarAcc ++ [$%| Result]]).
      │ │ │ -
      │ │ │ -copy_file(Src, Dest) ->
      │ │ │ -    copy_file(Src, Dest, []).
      │ │ │ -
      │ │ │ -copy_file(Src, Dest, Opts) ->
      │ │ │ -    {ok,_} = file:copy(Src, Dest),
      │ │ │ -    case lists:member(preserve, Opts) of
      │ │ │ +subst_var([C| Rest], Vars, Result, VarAcc) ->
      │ │ │ +    subst_var(Rest, Vars, Result, [C| VarAcc]);
      │ │ │ +subst_var([], Vars, Result, VarAcc) ->
      │ │ │ +    subst([], Vars, [VarAcc ++ [$%| Result]]).
      │ │ │ +
      │ │ │ +copy_file(Src, Dest) ->
      │ │ │ +    copy_file(Src, Dest, []).
      │ │ │ +
      │ │ │ +copy_file(Src, Dest, Opts) ->
      │ │ │ +    {ok,_} = file:copy(Src, Dest),
      │ │ │ +    case lists:member(preserve, Opts) of
      │ │ │          true ->
      │ │ │ -            {ok, FileInfo} = file:read_file_info(Src),
      │ │ │ -            file:write_file_info(Dest, FileInfo);
      │ │ │ +            {ok, FileInfo} = file:read_file_info(Src),
      │ │ │ +            file:write_file_info(Dest, FileInfo);
      │ │ │          false ->
      │ │ │              ok
      │ │ │      end.
      │ │ │  
      │ │ │ -write_file(FName, Conts) ->
      │ │ │ -    Enc = file:native_name_encoding(),
      │ │ │ -    {ok, Fd} = file:open(FName, [write]),
      │ │ │ -    file:write(Fd, unicode:characters_to_binary(Conts,Enc,Enc)),
      │ │ │ -    file:close(Fd).
      │ │ │ -
      │ │ │ -read_txt_file(File) ->
      │ │ │ -    {ok, Bin} = file:read_file(File),
      │ │ │ -    {ok, binary_to_list(Bin)}.
      │ │ │ -
      │ │ │ -remove_dir_tree(Dir) ->
      │ │ │ -    remove_all_files(".", [Dir]).
      │ │ │ -
      │ │ │ -remove_all_files(Dir, Files) ->
      │ │ │ -    lists:foreach(fun(File) ->
      │ │ │ -                          FilePath = filename:join([Dir, File]),
      │ │ │ -                          case filelib:is_dir(FilePath) of
      │ │ │ +write_file(FName, Conts) ->
      │ │ │ +    Enc = file:native_name_encoding(),
      │ │ │ +    {ok, Fd} = file:open(FName, [write]),
      │ │ │ +    file:write(Fd, unicode:characters_to_binary(Conts,Enc,Enc)),
      │ │ │ +    file:close(Fd).
      │ │ │ +
      │ │ │ +read_txt_file(File) ->
      │ │ │ +    {ok, Bin} = file:read_file(File),
      │ │ │ +    {ok, binary_to_list(Bin)}.
      │ │ │ +
      │ │ │ +remove_dir_tree(Dir) ->
      │ │ │ +    remove_all_files(".", [Dir]).
      │ │ │ +
      │ │ │ +remove_all_files(Dir, Files) ->
      │ │ │ +    lists:foreach(fun(File) ->
      │ │ │ +                          FilePath = filename:join([Dir, File]),
      │ │ │ +                          case filelib:is_dir(FilePath) of
      │ │ │                                true ->
      │ │ │ -                                  {ok, DirFiles} = file:list_dir(FilePath),
      │ │ │ -                                  remove_all_files(FilePath, DirFiles),
      │ │ │ -                                  file:del_dir(FilePath);
      │ │ │ +                                  {ok, DirFiles} = file:list_dir(FilePath),
      │ │ │ +                                  remove_all_files(FilePath, DirFiles),
      │ │ │ +                                  file:del_dir(FilePath);
      │ │ │                                _ ->
      │ │ │ -                                  file:delete(FilePath)
      │ │ │ +                                  file:delete(FilePath)
      │ │ │                            end
      │ │ │ -                  end, Files).
      │ │ │ + end, Files).
      │ │ │ │ │ │ │ │ │
      │ │ │
      │ │ │ │ │ │ │ │ │ Representation of Floating Point Numbers │ │ │ │ │ │

      When working with floats you may not see what you expect when printing or doing │ │ │ arithmetic operations. This is because floats are represented by a fixed number │ │ │ of bits in a base-2 system while printed floats are represented with a base-10 │ │ │ system. Erlang uses 64-bit floats. Here are examples of this phenomenon:

      1> 0.1+0.2.
      │ │ │ -0.30000000000000004

      The real numbers 0.1 and 0.2 cannot be represented exactly as floats.

      1> {36028797018963968.0, 36028797018963968 == 36028797018963968.0,
      │ │ │ -  36028797018963970.0, 36028797018963970 == 36028797018963970.0}.
      │ │ │ -{3.602879701896397e16, true,
      │ │ │ - 3.602879701896397e16, false}.

      The value 36028797018963968 can be represented exactly as a float value but │ │ │ +0.30000000000000004

    The real numbers 0.1 and 0.2 cannot be represented exactly as floats.

    1> {36028797018963968.0, 36028797018963968 == 36028797018963968.0,
    │ │ │ +  36028797018963970.0, 36028797018963970 == 36028797018963970.0}.
    │ │ │ +{3.602879701896397e16, true,
    │ │ │ + 3.602879701896397e16, false}.

    The value 36028797018963968 can be represented exactly as a float value but │ │ │ Erlang's pretty printer rounds 36028797018963968.0 to 3.602879701896397e16 │ │ │ (=36028797018963970.0) as all values in the range │ │ │ [36028797018963966.0, 36028797018963972.0] are represented by │ │ │ 36028797018963968.0.

    For more information about floats and issues with them see:

    If you need to work with exact decimal fractions, for instance to represent │ │ │ money, it is recommended to use a library that handles that, or work in │ │ │ cents instead of dollars or euros so that decimal fractions are not needed.

    Also note that Erlang's floats do not exactly match IEEE 754 floats, │ │ │ in that neither Inf nor NaN are supported in Erlang. Any │ │ │ @@ -244,52 +244,52 @@ │ │ │ by eight are called binaries.

    Examples:

    1> <<10,20>>.
    │ │ │  <<10,20>>
    │ │ │  2> <<"ABC">>.
    │ │ │  <<"ABC">>
    │ │ │  3> <<1:1,0:1>>.
    │ │ │  <<2:2>>

    The is_bitstring/1 BIF tests whether a │ │ │ term is a bit string, and the is_binary/1 │ │ │ -BIF tests whether a term is a binary.

    Examples:

    1> is_bitstring(<<1:1>>).
    │ │ │ +BIF tests whether a term is a binary.

    Examples:

    1> is_bitstring(<<1:1>>).
    │ │ │  true
    │ │ │ -2> is_binary(<<1:1>>).
    │ │ │ +2> is_binary(<<1:1>>).
    │ │ │  false
    │ │ │ -3> is_binary(<<42>>).
    │ │ │ +3> is_binary(<<42>>).
    │ │ │  true
    │ │ │  

    For more examples, see Programming Examples.

    │ │ │ │ │ │ │ │ │ │ │ │ Reference │ │ │

    │ │ │

    A term that is unique │ │ │ among connected nodes. A reference is created by calling the │ │ │ make_ref/0 BIF. The │ │ │ is_reference/1 BIF tests whether a term │ │ │ -is a reference.

    Examples:

    1> Ref = make_ref().
    │ │ │ +is a reference.

    Examples:

    1> Ref = make_ref().
    │ │ │  #Ref<0.76482849.3801088007.198204>
    │ │ │ -2> is_reference(Ref).
    │ │ │ +2> is_reference(Ref).
    │ │ │  true

    │ │ │ │ │ │ │ │ │ │ │ │ Fun │ │ │

    │ │ │

    A fun is a functional object. Funs make it possible to create an anonymous │ │ │ function and pass the function itself — not its name — as argument to other │ │ │ -functions.

    Examples:

    1> Fun1 = fun (X) -> X+1 end.
    │ │ │ +functions.

    Examples:

    1> Fun1 = fun (X) -> X+1 end.
    │ │ │  #Fun<erl_eval.6.39074546>
    │ │ │ -2> Fun1(2).
    │ │ │ +2> Fun1(2).
    │ │ │  3

    The is_function/1 and is_function/2 │ │ │ -BIFs tests whether a term is a fun.

    Examples:

    1> F = fun() -> ok end.
    │ │ │ +BIFs tests whether a term is a fun.

    Examples:

    1> F = fun() -> ok end.
    │ │ │  #Fun<erl_eval.43.105768164>
    │ │ │ -2> is_function(F).
    │ │ │ +2> is_function(F).
    │ │ │  true
    │ │ │ -3> is_function(F, 0).
    │ │ │ +3> is_function(F, 0).
    │ │ │  true
    │ │ │ -4> is_function(F, 1).
    │ │ │ +4> is_function(F, 1).
    │ │ │  false

    Read more about funs in Fun Expressions. For more │ │ │ examples, see Programming Examples.

    │ │ │ │ │ │ │ │ │ │ │ │ Port Identifier │ │ │

    │ │ │ @@ -307,94 +307,94 @@ │ │ │ for a new process after a while.

    The BIF self/0 returns the Pid of the calling process. When │ │ │ creating a new process, the parent │ │ │ process will be able to get the Pid of the child process either via the return │ │ │ value, as is the case when calling the spawn/3 BIF, or via │ │ │ a message, which is the case when calling the │ │ │ spawn_request/5 BIF. A Pid is typically used when │ │ │ when sending a process a signal. The │ │ │ -is_pid/1 BIF tests whether a term is a Pid.

    Example:

    -module(m).
    │ │ │ --export([loop/0]).
    │ │ │ +is_pid/1 BIF tests whether a term is a Pid.

    Example:

    -module(m).
    │ │ │ +-export([loop/0]).
    │ │ │  
    │ │ │ -loop() ->
    │ │ │ +loop() ->
    │ │ │      receive
    │ │ │          who_are_you ->
    │ │ │ -            io:format("I am ~p~n", [self()]),
    │ │ │ -            loop()
    │ │ │ +            io:format("I am ~p~n", [self()]),
    │ │ │ +            loop()
    │ │ │      end.
    │ │ │  
    │ │ │ -1> P = spawn(m, loop, []).
    │ │ │ +1> P = spawn(m, loop, []).
    │ │ │  <0.58.0>
    │ │ │  2> P ! who_are_you.
    │ │ │  I am <0.58.0>
    │ │ │  who_are_you

    Read more about processes in Processes.

    │ │ │ │ │ │ │ │ │ │ │ │ Tuple │ │ │

    │ │ │

    A tuple is a compound data type with a fixed number of terms:

    {Term1,...,TermN}

    Each term Term in the tuple is called an element. The number of elements is │ │ │ -said to be the size of the tuple.

    There exists a number of BIFs to manipulate tuples.

    Examples:

    1> P = {adam,24,{july,29}}.
    │ │ │ -{adam,24,{july,29}}
    │ │ │ -2> element(1,P).
    │ │ │ +said to be the size of the tuple.

    There exists a number of BIFs to manipulate tuples.

    Examples:

    1> P = {adam,24,{july,29}}.
    │ │ │ +{adam,24,{july,29}}
    │ │ │ +2> element(1,P).
    │ │ │  adam
    │ │ │ -3> element(3,P).
    │ │ │ -{july,29}
    │ │ │ -4> P2 = setelement(2,P,25).
    │ │ │ -{adam,25,{july,29}}
    │ │ │ -5> tuple_size(P).
    │ │ │ +3> element(3,P).
    │ │ │ +{july,29}
    │ │ │ +4> P2 = setelement(2,P,25).
    │ │ │ +{adam,25,{july,29}}
    │ │ │ +5> tuple_size(P).
    │ │ │  3
    │ │ │ -6> tuple_size({}).
    │ │ │ +6> tuple_size({}).
    │ │ │  0
    │ │ │ -7> is_tuple({a,b,c}).
    │ │ │ +7> is_tuple({a,b,c}).
    │ │ │  true

    │ │ │ │ │ │ │ │ │ │ │ │ Map │ │ │

    │ │ │

    A map is a compound data type with a variable number of key-value associations:

    #{Key1 => Value1, ..., KeyN => ValueN}

    Each key-value association in the map is called an association pair. The key │ │ │ and value parts of the pair are called elements. The number of association │ │ │ -pairs is said to be the size of the map.

    There exists a number of BIFs to manipulate maps.

    Examples:

    1> M1 = #{name => adam, age => 24, date => {july,29}}.
    │ │ │ -#{age => 24,date => {july,29},name => adam}
    │ │ │ -2> maps:get(name, M1).
    │ │ │ +pairs is said to be the size of the map.

    There exists a number of BIFs to manipulate maps.

    Examples:

    1> M1 = #{name => adam, age => 24, date => {july,29}}.
    │ │ │ +#{age => 24,date => {july,29},name => adam}
    │ │ │ +2> maps:get(name, M1).
    │ │ │  adam
    │ │ │ -3> maps:get(date, M1).
    │ │ │ -{july,29}
    │ │ │ -4> M2 = maps:update(age, 25, M1).
    │ │ │ -#{age => 25,date => {july,29},name => adam}
    │ │ │ -5> map_size(M).
    │ │ │ +3> maps:get(date, M1).
    │ │ │ +{july,29}
    │ │ │ +4> M2 = maps:update(age, 25, M1).
    │ │ │ +#{age => 25,date => {july,29},name => adam}
    │ │ │ +5> map_size(M).
    │ │ │  3
    │ │ │ -6> map_size(#{}).
    │ │ │ +6> map_size(#{}).
    │ │ │  0

    A collection of maps processing functions are found in module maps │ │ │ in STDLIB.

    Read more about maps in Map Expressions.

    Change

    Maps were introduced as an experimental feature in Erlang/OTP R17. Their │ │ │ functionality was extended and became fully supported in Erlang/OTP 18.

    │ │ │ │ │ │ │ │ │ │ │ │ List │ │ │

    │ │ │

    A list is a compound data type with a variable number of terms.

    [Term1,...,TermN]

    Each term Term in the list is called an element. The number of elements is │ │ │ said to be the length of the list.

    Formally, a list is either the empty list [] or consists of a head (first │ │ │ element) and a tail (remainder of the list). The tail is also a list. The │ │ │ latter can be expressed as [H|T]. The notation [Term1,...,TermN] above is │ │ │ equivalent with the list [Term1|[...|[TermN|[]]]].

    Example:

    [] is a list, thus
    [c|[]] is a list, thus
    [b|[c|[]]] is a list, thus
    [a|[b|[c|[]]]] is a list, or in short [a,b,c]

    A list where the tail is a list is sometimes called a proper list. It is │ │ │ allowed to have a list where the tail is not a list, for example, [a|b]. │ │ │ -However, this type of list is of little practical use.

    Examples:

    1> L1 = [a,2,{c,4}].
    │ │ │ -[a,2,{c,4}]
    │ │ │ -2> [H|T] = L1.
    │ │ │ -[a,2,{c,4}]
    │ │ │ +However, this type of list is of little practical use.

    Examples:

    1> L1 = [a,2,{c,4}].
    │ │ │ +[a,2,{c,4}]
    │ │ │ +2> [H|T] = L1.
    │ │ │ +[a,2,{c,4}]
    │ │ │  3> H.
    │ │ │  a
    │ │ │  4> T.
    │ │ │ -[2,{c,4}]
    │ │ │ -5> L2 = [d|T].
    │ │ │ -[d,2,{c,4}]
    │ │ │ -6> length(L1).
    │ │ │ +[2,{c,4}]
    │ │ │ +5> L2 = [d|T].
    │ │ │ +[d,2,{c,4}]
    │ │ │ +6> length(L1).
    │ │ │  3
    │ │ │ -7> length([]).
    │ │ │ +7> length([]).
    │ │ │  0

    A collection of list processing functions are found in module │ │ │ lists in STDLIB.

    │ │ │ │ │ │ │ │ │ │ │ │ String │ │ │

    │ │ │ @@ -514,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 in runtime by calling net_kernel:start/1.

    Example:

    % erl
    │ │ │ -1> node().
    │ │ │ +1> node().
    │ │ │  nonode@nohost
    │ │ │ -2> net_kernel:start([dilbert,shortnames]).
    │ │ │ -{ok,<0.102.0>}
    │ │ │ -(dilbert@uab)3> node().
    │ │ │ +2> net_kernel:start([dilbert,shortnames]).
    │ │ │ +{ok,<0.102.0>}
    │ │ │ +(dilbert@uab)3> node().
    │ │ │  dilbert@uab

    Note

    A node with a long node name cannot communicate with a node with a short node │ │ │ name.

    │ │ │ │ │ │ │ │ │ │ │ │ Node Connections │ │ │

    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/distributed_applications.html │ │ │ @@ -150,36 +150,36 @@ │ │ │ (within the time-out specified by sync_nodes_timeout).
  • sync_nodes_timeout = integer() | infinity - Specifies how many milliseconds │ │ │ to wait for the other nodes to start.

  • When started, the node waits for all nodes specified by sync_nodes_mandatory │ │ │ and sync_nodes_optional to come up. When all nodes are up, or when all │ │ │ mandatory nodes are up and the time specified by sync_nodes_timeout has │ │ │ elapsed, all applications start. If not all mandatory nodes are up, the node │ │ │ terminates.

    Example:

    An application myapp is to run at the node cp1@cave. If this node goes down, │ │ │ myapp is to be restarted at cp2@cave or cp3@cave. A system configuration │ │ │ -file cp1.config for cp1@cave can look as follows:

    [{kernel,
    │ │ │ -  [{distributed, [{myapp, 5000, [cp1@cave, {cp2@cave, cp3@cave}]}]},
    │ │ │ -   {sync_nodes_mandatory, [cp2@cave, cp3@cave]},
    │ │ │ -   {sync_nodes_timeout, 5000}
    │ │ │ -  ]
    │ │ │ - }
    │ │ │ -].

    The system configuration files for cp2@cave and cp3@cave are identical, │ │ │ +file cp1.config for cp1@cave can look as follows:

    [{kernel,
    │ │ │ +  [{distributed, [{myapp, 5000, [cp1@cave, {cp2@cave, cp3@cave}]}]},
    │ │ │ +   {sync_nodes_mandatory, [cp2@cave, cp3@cave]},
    │ │ │ +   {sync_nodes_timeout, 5000}
    │ │ │ +  ]
    │ │ │ + }
    │ │ │ +].

    The system configuration files for cp2@cave and cp3@cave are identical, │ │ │ except for the list of mandatory nodes, which is to be [cp1@cave, cp3@cave] │ │ │ for cp2@cave and [cp1@cave, cp2@cave] for cp3@cave.

    Note

    All involved nodes must have the same value for distributed and │ │ │ sync_nodes_timeout. Otherwise the system behavior is undefined.

    │ │ │ │ │ │ │ │ │ │ │ │ Starting and Stopping Distributed Applications │ │ │

    │ │ │

    When all involved (mandatory) nodes have been started, the distributed │ │ │ application can be started by calling application:start(Application) at all │ │ │ of these nodes.

    A boot script (see Releases) can be used that │ │ │ automatically starts the application.

    The application is started at the first operational node that is listed in the │ │ │ list of nodes in the distributed configuration parameter. The application is │ │ │ started as usual. That is, an application master is created and calls the │ │ │ -application callback function:

    Module:start(normal, StartArgs)

    Example:

    Continuing the example from the previous section, the three nodes are started, │ │ │ +application callback function:

    Module:start(normal, StartArgs)

    Example:

    Continuing the example from the previous section, the three nodes are started, │ │ │ specifying the system configuration file:

    > erl -sname cp1 -config cp1
    │ │ │  > erl -sname cp2 -config cp2
    │ │ │  > erl -sname cp3 -config cp3

    When all nodes are operational, myapp can be started. This is achieved by │ │ │ calling application:start(myapp) at all three nodes. It is then started at │ │ │ cp1, as shown in the following figure:

    Application myapp - Situation 1

    Similarly, the application must be stopped by calling │ │ │ application:stop(Application) at all involved nodes.

    │ │ │ │ │ │ @@ -187,30 +187,30 @@ │ │ │ │ │ │ Failover │ │ │

    │ │ │

    If the node where the application is running goes down, the application is │ │ │ restarted (after the specified time-out) at the first operational node that is │ │ │ listed in the list of nodes in the distributed configuration parameter. This │ │ │ is called a failover.

    The application is started the normal way at the new node, that is, by the │ │ │ -application master calling:

    Module:start(normal, StartArgs)

    An exception is if the application has the start_phases key defined (see │ │ │ +application master calling:

    Module:start(normal, StartArgs)

    An exception is if the application has the start_phases key defined (see │ │ │ Included Applications). The application is then │ │ │ -instead started by calling:

    Module:start({failover, Node}, StartArgs)

    Here Node is the terminated node.

    Example:

    If cp1 goes down, the system checks which one of the other nodes, cp2 or │ │ │ +instead started by calling:

    Module:start({failover, Node}, StartArgs)

    Here Node is the terminated node.

    Example:

    If cp1 goes down, the system checks which one of the other nodes, cp2 or │ │ │ cp3, has the least number of running applications, but waits for 5 seconds for │ │ │ cp1 to restart. If cp1 does not restart and cp2 runs fewer applications │ │ │ than cp3, myapp is restarted on cp2.

    Application myapp - Situation 2

    Suppose now that cp2 goes also down and does not restart within 5 seconds. │ │ │ myapp is now restarted on cp3.

    Application myapp - Situation 3

    │ │ │ │ │ │ │ │ │ │ │ │ Takeover │ │ │

    │ │ │

    If a node is started, which has higher priority according to distributed than │ │ │ the node where a distributed application is running, the application is │ │ │ restarted at the new node and stopped at the old node. This is called a │ │ │ -takeover.

    The application is started by the application master calling:

    Module:start({takeover, Node}, StartArgs)

    Here Node is the old node.

    Example:

    If myapp is running at cp3, and if cp2 now restarts, it does not restart │ │ │ +takeover.

    The application is started by the application master calling:

    Module:start({takeover, Node}, StartArgs)

    Here Node is the old node.

    Example:

    If myapp is running at cp3, and if cp2 now restarts, it does not restart │ │ │ myapp, as the order between the cp2 and cp3 nodes is undefined.

    Application myapp - Situation 4

    However, if cp1 also restarts, the function application:takeover/2 moves │ │ │ myapp to cp1, as cp1 has a higher priority than cp3 for this │ │ │ application. In this case, Module:start({takeover, cp3@cave}, StartArgs) is │ │ │ executed at cp1 to start the application.

    Application myapp - Situation 5

    │ │ │
    │ │ │ │ │ │
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/documentation.html │ │ │ @@ -112,23 +112,23 @@ │ │ │ │ │ │ │ │ │ │ │ │ Documentation │ │ │ │ │ │ │ │ │

    Documentation in Erlang is done through the -moduledoc and -doc │ │ │ -attributes. For example:

    -module(arith).
    │ │ │ +attributes. For example:

    -module(arith).
    │ │ │  -moduledoc """
    │ │ │  A module for basic arithmetic.
    │ │ │  """.
    │ │ │  
    │ │ │ --export([add/2]).
    │ │ │ +-export([add/2]).
    │ │ │  
    │ │ │  -doc "Adds two numbers.".
    │ │ │ -add(One, Two) -> One + Two.

    The -moduledoc attribute has to be located before the first -doc attribute │ │ │ +add(One, Two) -> One + Two.

    The -moduledoc attribute has to be located before the first -doc attribute │ │ │ or function declaration. It documents the overall purpose of the module.

    The -doc attribute always precedes the function or │ │ │ attribute it documents. The │ │ │ attributes that can be documented are │ │ │ user-defined types │ │ │ (-type and -opaque) and │ │ │ behaviour module attributes │ │ │ (-callback).

    By default the format used for documentation attributes is │ │ │ @@ -140,55 +140,55 @@ │ │ │ Documentation Attributes.

    -doc attributes have been available since Erlang/OTP 27.

    │ │ │ │ │ │ │ │ │ │ │ │ Documentation metadata │ │ │

    │ │ │

    It is possible to add metadata to the documentation entry. You do this by adding │ │ │ -a -moduledoc or -doc attribute with a map as argument. For example:

    -module(arith).
    │ │ │ +a -moduledoc or -doc attribute with a map as argument. For example:

    -module(arith).
    │ │ │  -moduledoc """
    │ │ │  A module for basic arithmetic.
    │ │ │  """.
    │ │ │ --moduledoc #{since => "1.0"}.
    │ │ │ +-moduledoc #{since => "1.0"}.
    │ │ │  
    │ │ │ --export([add/2]).
    │ │ │ +-export([add/2]).
    │ │ │  
    │ │ │  -doc "Adds two numbers.".
    │ │ │ --doc(#{since => "1.0"}).
    │ │ │ -add(One, Two) -> One + Two.

    The metadata is used by documentation tools to provide extra information to the │ │ │ +-doc(#{since => "1.0"}). │ │ │ +add(One, Two) -> One + Two.

    The metadata is used by documentation tools to provide extra information to the │ │ │ user. There can be multiple metadata documentation entries, in which case the │ │ │ maps will be merged with the latest taking precedence if there are duplicate │ │ │ keys. Example:

    -doc "Adds two numbers.".
    │ │ │ --doc #{since => "1.0", author => "Joe"}.
    │ │ │ --doc #{since => "2.0"}.
    │ │ │ -add(One, Two) -> One + Two.

    This will result in a metadata entry of #{since => "2.0", author => "Joe"}.

    The keys and values in the metadata map can be any type, but it is recommended │ │ │ +-doc #{since => "1.0", author => "Joe"}. │ │ │ +-doc #{since => "2.0"}. │ │ │ +add(One, Two) -> One + Two.

    This will result in a metadata entry of #{since => "2.0", author => "Joe"}.

    The keys and values in the metadata map can be any type, but it is recommended │ │ │ that only atoms are used for keys and │ │ │ strings for the values.

    │ │ │ │ │ │ │ │ │ │ │ │ External documentation files │ │ │

    │ │ │

    The -moduledoc and -doc can also be placed in external files. To do so use │ │ │ -doc {file, "path/to/doc.md"} to point to the documentation. The path used is │ │ │ relative to the file where the -doc attribute is located. For example:

    %% doc/add.md
    │ │ │  Adds two numbers.

    and

    %% src/arith.erl
    │ │ │ --doc({file, "../doc/add.md"}).
    │ │ │ -add(One, Two) -> One + Two.

    │ │ │ +-doc({file, "../doc/add.md"}). │ │ │ +add(One, Two) -> One + Two.

    │ │ │ │ │ │ │ │ │ │ │ │ Documenting a module │ │ │

    │ │ │

    The module description should include details on how to use the API and examples │ │ │ of the different functions working together. Here is a good place to use images │ │ │ and other diagrams to better show the usage of the module. Instead of writing a │ │ │ long text in the moduledoc attribute, it could be better to break it out into │ │ │ an external page.

    The moduledoc attribute should start with a short paragraph describing the │ │ │ -module and then go into greater details. For example:

    -module(arith).
    │ │ │ +module and then go into greater details. For example:

    -module(arith).
    │ │ │  -moduledoc """
    │ │ │     A module for basic arithmetic.
    │ │ │  
    │ │ │     This module can be used to add and subtract values. For example:
    │ │ │  
    │ │ │     ```erlang
    │ │ │     1> arith:substract(arith:add(2, 3), 1).
    │ │ │ @@ -203,96 +203,96 @@
    │ │ │  

    There are three reserved metadata keys for -moduledoc:

    • since => unicode:chardata() - Shows in which version of the application the module was added. │ │ │ If this is added, all functions, types, and callbacks within will also receive │ │ │ the same since value unless specified in the metadata of the function, type │ │ │ or callback.
    • deprecated => unicode:chardata() - Shows a text in the documentation explaining that it is │ │ │ deprecated and what to use instead.
    • format => unicode:chardata() - The format to use for all documentation in this module. The │ │ │ default is text/markdown. It should be written using the │ │ │ mime type │ │ │ -of the format.

    Example:

    -moduledoc {file, "../doc/arith.asciidoc"}.
    │ │ │ --moduledoc #{since => "0.1", format => "text/asciidoc"}.
    │ │ │ --moduledoc #{deprecated => "Use the Erlang arithmetic operators instead."}.

    │ │ │ +of the format.

    Example:

    -moduledoc {file, "../doc/arith.asciidoc"}.
    │ │ │ +-moduledoc #{since => "0.1", format => "text/asciidoc"}.
    │ │ │ +-moduledoc #{deprecated => "Use the Erlang arithmetic operators instead."}.

    │ │ │ │ │ │ │ │ │ │ │ │ Documenting functions, user-defined types, and callbacks │ │ │

    │ │ │

    Functions, types, and callbacks can be documented using the -doc attribute. │ │ │ Each entry should start with a short paragraph describing the purpose of entity, │ │ │ and then go into greater detail in needed.

    It is not recommended to include images or diagrams in this documentation as it │ │ │ is used by IDEs and c:h/1 to show the documentation to the user.

    For example:

    -doc """
    │ │ │  A number that can be used by the arith module.
    │ │ │  
    │ │ │  We use a special number here so that we know
    │ │ │  that this number comes from this module.
    │ │ │  """.
    │ │ │ --opaque number() :: {arith, erlang:number()}.
    │ │ │ +-opaque number() :: {arith, erlang:number()}.
    │ │ │  
    │ │ │  -doc """
    │ │ │  Adds two numbers.
    │ │ │  
    │ │ │  ### Example:
    │ │ │  
    │ │ │  ```
    │ │ │  1> arith:add(arith:number(1), arith:number(2)). {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 which version of the application the │ │ │ module was added.

    • deprecated => unicode:chardata() - Shows a text in the documentation │ │ │ explaining that it is deprecated and what to use instead. The compiler will │ │ │ automatically insert this key if there is a -deprecated attribute marking a │ │ │ function as deprecated.

    • group => unicode:chardata() - A group that the function, type or callback belongs to. │ │ │ It allows tooling, such as shell autocompletion and documentation generators, to list all │ │ │ entries within the same group together, often using the group name as an indicator.

    • equiv => unicode:chardata() | F/A | F(...) - Notes that this function is equivalent to │ │ │ another function in this module. The equivalence can be described using either │ │ │ -Func/Arity, Func(Args) or a unicode string. For example:

      -doc #{equiv => add/3}.
      │ │ │ -add(One, Two) -> add(One, Two, []).
      │ │ │ -add(One, Two, Options) -> ...

      or

      -doc #{equiv => add(One, Two, [])}.
      │ │ │ --spec add(One :: number(), Two :: number()) -> number().
      │ │ │ -add(One, Two) -> add(One, Two, []).
      │ │ │ -add(One, Two, Options) -> ...

      The entry into the EEP-48 doc chunk metadata is │ │ │ +Func/Arity, Func(Args) or a unicode string. For example:

      -doc #{equiv => add/3}.
      │ │ │ +add(One, Two) -> add(One, Two, []).
      │ │ │ +add(One, Two, Options) -> ...

      or

      -doc #{equiv => add(One, Two, [])}.
      │ │ │ +-spec add(One :: number(), Two :: number()) -> number().
      │ │ │ +add(One, Two) -> add(One, Two, []).
      │ │ │ +add(One, Two, Options) -> ...

      The entry into the EEP-48 doc chunk metadata is │ │ │ the value converted to a string.

    • exported => boolean() - A boolean/0 signifying if the entry is exported │ │ │ or not. This value is automatically set by the compiler and should not be set │ │ │ by the user.

    │ │ │ │ │ │ │ │ │ │ │ │ Doc signatures │ │ │

    │ │ │

    The doc signature is a short text shown to describe the function and its arguments. │ │ │ By default it is determined by looking at the names of the arguments in the │ │ │ --spec or function. For example:

    add(One, Two) -> One + Two.
    │ │ │ +-spec or function. For example:

    add(One, Two) -> One + Two.
    │ │ │  
    │ │ │ --spec sub(One :: integer(), Two :: integer()) -> integer().
    │ │ │ -sub(X, Y) -> X - Y.

    will have a signature of add(One, Two) and sub(One, Two).

    For types or callbacks, the signature is derived from the type or callback │ │ │ -specification. For example:

    -type number(Value) :: {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.

    Will create the signature add(One, Two). The signature will be removed from the │ │ │ +add(A, B) -> A + B.

    Will create the signature add(One, Two). The signature will be removed from the │ │ │ documentation string, so in the example above only the text "Adds two numbers" │ │ │ will be part of the documentation. This works for functions, types, and │ │ │ callbacks.

    │ │ │ │ │ │ │ │ │ │ │ │ Compiling and getting documentation │ │ │ @@ -377,21 +377,21 @@ │ │ │ Using ExDoc to generate HTML/ePub documentation │ │ │

    │ │ │

    ExDoc has built-in support to generate │ │ │ documentation from Markdown. The simplest way is by using the │ │ │ rebar3_ex_doc plugin. To set up a │ │ │ rebar3 project to use ExDoc to generate │ │ │ documentation add the following to your rebar3.config.

    %% Enable the plugin
    │ │ │ -{plugins, [rebar3_ex_doc]}.
    │ │ │ +{plugins, [rebar3_ex_doc]}.
    │ │ │  
    │ │ │ -{ex_doc, [
    │ │ │ -  {extras, ["README.md"]},
    │ │ │ -  {main, "README.md"},
    │ │ │ -  {source_url, "https://github.com/namespace/your_app"}
    │ │ │ -]}.

    When configured you can run rebar3 ex_doc to generate the │ │ │ +{ex_doc, [ │ │ │ + {extras, ["README.md"]}, │ │ │ + {main, "README.md"}, │ │ │ + {source_url, "https://github.com/namespace/your_app"} │ │ │ +]}.

    When configured you can run rebar3 ex_doc to generate the │ │ │ documentation to doc/index.html. For more details and options see │ │ │ the rebar3_ex_doc documentation.

    You can also download the │ │ │ release escript bundle from │ │ │ github and run it from the command line. The documentation for using the escript │ │ │ is found by running ex_doc --help.

    If you are writing documentation that will be using │ │ │ ExDoc to generate HTML/ePub it is highly │ │ │ recommended to read its documentation.

    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/drivers.html │ │ │ @@ -122,23 +122,23 @@ │ │ │ Drivers and Concurrency │ │ │ │ │ │

    The runtime system always takes a lock before running any code in a driver.

    By default, that lock is at the driver level, that is, if several ports have │ │ │ been opened to the same driver, only code for one port at the same time can be │ │ │ running.

    A driver can be configured to have one lock for each port instead.

    If a driver is used in a functional way (that is, holds no state, but only does │ │ │ some heavy calculation and returns a result), several ports with registered │ │ │ names can be opened beforehand, and the port to be used can be chosen based on │ │ │ -the scheduler ID as follows:

    -define(PORT_NAMES(),
    │ │ │ -	{some_driver_01, some_driver_02, some_driver_03, some_driver_04,
    │ │ │ +the scheduler ID as follows:

    -define(PORT_NAMES(),
    │ │ │ +	{some_driver_01, some_driver_02, some_driver_03, some_driver_04,
    │ │ │  	 some_driver_05, some_driver_06, some_driver_07, some_driver_08,
    │ │ │  	 some_driver_09, some_driver_10, some_driver_11, some_driver_12,
    │ │ │ -	 some_driver_13, some_driver_14, some_driver_15, some_driver_16}).
    │ │ │ +	 some_driver_13, some_driver_14, some_driver_15, some_driver_16}).
    │ │ │  
    │ │ │ -client_port() ->
    │ │ │ -    element(erlang:system_info(scheduler_id) rem tuple_size(?PORT_NAMES()) + 1,
    │ │ │ -	    ?PORT_NAMES()).

    As long as there are no more than 16 schedulers, there will never be any lock │ │ │ +client_port() -> │ │ │ + element(erlang:system_info(scheduler_id) rem tuple_size(?PORT_NAMES()) + 1, │ │ │ + ?PORT_NAMES()).

    As long as there are no more than 16 schedulers, there will never be any lock │ │ │ contention on the port lock for the driver.

    │ │ │ │ │ │ │ │ │ │ │ │ Avoiding Copying Binaries When Calling a Driver │ │ │

    │ │ │

    There are basically two ways to avoid copying a binary that is sent to a driver:

    • If the Data argument for port_control/3 is a │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/eff_guide_functions.html │ │ │ @@ -122,67 +122,67 @@ │ │ │ Pattern Matching │ │ │ │ │ │

      Pattern matching in function head as well as in case and receive clauses are │ │ │ optimized by the compiler. With a few exceptions, there is nothing to gain by │ │ │ rearranging clauses.

      One exception is pattern matching of binaries. The compiler does not rearrange │ │ │ clauses that match binaries. Placing the clause that matches against the empty │ │ │ binary last is usually slightly faster than placing it first.

      The following is a rather unnatural example to show another exception where │ │ │ -rearranging clauses is beneficial:

      DO NOT

      atom_map1(one) -> 1;
      │ │ │ -atom_map1(two) -> 2;
      │ │ │ -atom_map1(three) -> 3;
      │ │ │ -atom_map1(Int) when is_integer(Int) -> Int;
      │ │ │ -atom_map1(four) -> 4;
      │ │ │ -atom_map1(five) -> 5;
      │ │ │ -atom_map1(six) -> 6.

      The problem is the clause with the variable Int. As a variable can match │ │ │ +rearranging clauses is beneficial:

      DO NOT

      atom_map1(one) -> 1;
      │ │ │ +atom_map1(two) -> 2;
      │ │ │ +atom_map1(three) -> 3;
      │ │ │ +atom_map1(Int) when is_integer(Int) -> Int;
      │ │ │ +atom_map1(four) -> 4;
      │ │ │ +atom_map1(five) -> 5;
      │ │ │ +atom_map1(six) -> 6.

      The problem is the clause with the variable Int. As a variable can match │ │ │ anything, including the atoms four, five, and six, which the following │ │ │ clauses also match, the compiler must generate suboptimal code that executes as │ │ │ follows:

      • First, the input value is compared to one, two, and three (using a │ │ │ single instruction that does a binary search; thus, quite efficient even if │ │ │ there are many values) to select which one of the first three clauses to │ │ │ execute (if any).
      • If none of the first three clauses match, the fourth clause match as a │ │ │ variable always matches.
      • If the guard test is_integer(Int) succeeds, the fourth │ │ │ clause is executed.
      • If the guard test fails, the input value is compared to four, five, and │ │ │ six, and the appropriate clause is selected. (There is a function_clause │ │ │ -exception if none of the values matched.)

      Rewriting to either:

      DO

      atom_map2(one) -> 1;
      │ │ │ -atom_map2(two) -> 2;
      │ │ │ -atom_map2(three) -> 3;
      │ │ │ -atom_map2(four) -> 4;
      │ │ │ -atom_map2(five) -> 5;
      │ │ │ -atom_map2(six) -> 6;
      │ │ │ -atom_map2(Int) when is_integer(Int) -> Int.

      or:

      DO

      atom_map3(Int) when is_integer(Int) -> Int;
      │ │ │ -atom_map3(one) -> 1;
      │ │ │ -atom_map3(two) -> 2;
      │ │ │ -atom_map3(three) -> 3;
      │ │ │ -atom_map3(four) -> 4;
      │ │ │ -atom_map3(five) -> 5;
      │ │ │ -atom_map3(six) -> 6.

      gives slightly more efficient matching code.

      Another example:

      DO NOT

      map_pairs1(_Map, [], Ys) ->
      │ │ │ +exception if none of the values matched.)

    Rewriting to either:

    DO

    atom_map2(one) -> 1;
    │ │ │ +atom_map2(two) -> 2;
    │ │ │ +atom_map2(three) -> 3;
    │ │ │ +atom_map2(four) -> 4;
    │ │ │ +atom_map2(five) -> 5;
    │ │ │ +atom_map2(six) -> 6;
    │ │ │ +atom_map2(Int) when is_integer(Int) -> Int.

    or:

    DO

    atom_map3(Int) when is_integer(Int) -> Int;
    │ │ │ +atom_map3(one) -> 1;
    │ │ │ +atom_map3(two) -> 2;
    │ │ │ +atom_map3(three) -> 3;
    │ │ │ +atom_map3(four) -> 4;
    │ │ │ +atom_map3(five) -> 5;
    │ │ │ +atom_map3(six) -> 6.

    gives slightly more efficient matching code.

    Another example:

    DO NOT

    map_pairs1(_Map, [], Ys) ->
    │ │ │      Ys;
    │ │ │ -map_pairs1(_Map, Xs, []) ->
    │ │ │ +map_pairs1(_Map, Xs, []) ->
    │ │ │      Xs;
    │ │ │ -map_pairs1(Map, [X|Xs], [Y|Ys]) ->
    │ │ │ -    [Map(X, Y)|map_pairs1(Map, Xs, Ys)].

    The first argument is not a problem. It is variable, but it is a variable in │ │ │ +map_pairs1(Map, [X|Xs], [Y|Ys]) -> │ │ │ + [Map(X, Y)|map_pairs1(Map, Xs, Ys)].

    The first argument is not a problem. It is variable, but it is a variable in │ │ │ all clauses. The problem is the variable in the second argument, Xs, in the │ │ │ middle clause. Because the variable can match anything, the compiler is not │ │ │ allowed to rearrange the clauses, but must generate code that matches them in │ │ │ the order written.

    If the function is rewritten as follows, the compiler is free to rearrange the │ │ │ -clauses:

    DO

    map_pairs2(_Map, [], Ys) ->
    │ │ │ +clauses:

    DO

    map_pairs2(_Map, [], Ys) ->
    │ │ │      Ys;
    │ │ │ -map_pairs2(_Map, [_|_]=Xs, [] ) ->
    │ │ │ +map_pairs2(_Map, [_|_]=Xs, [] ) ->
    │ │ │      Xs;
    │ │ │ -map_pairs2(Map, [X|Xs], [Y|Ys]) ->
    │ │ │ -    [Map(X, Y)|map_pairs2(Map, Xs, Ys)].

    The compiler will generate code similar to this:

    DO NOT (already done by the compiler)

    explicit_map_pairs(Map, Xs0, Ys0) ->
    │ │ │ +map_pairs2(Map, [X|Xs], [Y|Ys]) ->
    │ │ │ +    [Map(X, Y)|map_pairs2(Map, Xs, Ys)].

    The compiler will generate code similar to this:

    DO NOT (already done by the compiler)

    explicit_map_pairs(Map, Xs0, Ys0) ->
    │ │ │      case Xs0 of
    │ │ │ -	[X|Xs] ->
    │ │ │ +	[X|Xs] ->
    │ │ │  	    case Ys0 of
    │ │ │ -		[Y|Ys] ->
    │ │ │ -		    [Map(X, Y)|explicit_map_pairs(Map, Xs, Ys)];
    │ │ │ -		[] ->
    │ │ │ +		[Y|Ys] ->
    │ │ │ +		    [Map(X, Y)|explicit_map_pairs(Map, Xs, Ys)];
    │ │ │ +		[] ->
    │ │ │  		    Xs0
    │ │ │  	    end;
    │ │ │ -	[] ->
    │ │ │ +	[] ->
    │ │ │  	    Ys0
    │ │ │      end.

    This is slightly faster for probably the most common case that the input lists │ │ │ are not empty or very short. (Another advantage is that Dialyzer can deduce a │ │ │ better type for the Xs variable.)

    │ │ │ │ │ │ │ │ │ │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/eff_guide_processes.html │ │ │ @@ -119,45 +119,45 @@ │ │ │ │ │ │ │ │ │ │ │ │ Creating an Erlang Process │ │ │

    │ │ │

    An Erlang process is lightweight compared to threads and processes in operating │ │ │ systems.

    A newly spawned Erlang process uses 327 words of memory. The size can be found │ │ │ -as follows:

    Erlang/OTP 27 [erts-14.2.3] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
    │ │ │ +as follows:

    Erlang/OTP 27 [erts-14.2.3] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
    │ │ │  
    │ │ │ -Eshell V14.2.3 (press Ctrl+G to abort, type help(). for help)
    │ │ │ -1> Fun = fun() -> receive after infinity -> ok end end.
    │ │ │ +Eshell V14.2.3 (press Ctrl+G to abort, type help(). for help)
    │ │ │ +1> Fun = fun() -> receive after infinity -> ok end end.
    │ │ │  #Fun<erl_eval.43.39164016>
    │ │ │ -2> {_,Bytes} = process_info(spawn(Fun), memory).
    │ │ │ -{memory,2616}
    │ │ │ -3> Bytes div erlang:system_info(wordsize).
    │ │ │ +2> {_,Bytes} = process_info(spawn(Fun), memory).
    │ │ │ +{memory,2616}
    │ │ │ +3> Bytes div erlang:system_info(wordsize).
    │ │ │  327

    The size includes 233 words for the heap area (which includes the stack). The │ │ │ garbage collector increases the heap as needed.

    The main (outer) loop for a process must be tail-recursive. Otherwise, the │ │ │ -stack grows until the process terminates.

    DO NOT

    loop() ->
    │ │ │ +stack grows until the process terminates.

    DO NOT

    loop() ->
    │ │ │    receive
    │ │ │ -     {sys, Msg} ->
    │ │ │ -         handle_sys_msg(Msg),
    │ │ │ -         loop();
    │ │ │ -     {From, Msg} ->
    │ │ │ -          Reply = handle_msg(Msg),
    │ │ │ +     {sys, Msg} ->
    │ │ │ +         handle_sys_msg(Msg),
    │ │ │ +         loop();
    │ │ │ +     {From, Msg} ->
    │ │ │ +          Reply = handle_msg(Msg),
    │ │ │            From ! Reply,
    │ │ │ -          loop()
    │ │ │ +          loop()
    │ │ │    end,
    │ │ │ -  io:format("Message is processed~n", []).

    The call to io:format/2 will never be executed, but a return address will │ │ │ + io:format("Message is processed~n", []).

    The call to io:format/2 will never be executed, but a return address will │ │ │ still be pushed to the stack each time loop/0 is called recursively. The │ │ │ -correct tail-recursive version of the function looks as follows:

    DO

    loop() ->
    │ │ │ +correct tail-recursive version of the function looks as follows:

    DO

    loop() ->
    │ │ │     receive
    │ │ │ -      {sys, Msg} ->
    │ │ │ -         handle_sys_msg(Msg),
    │ │ │ -         loop();
    │ │ │ -      {From, Msg} ->
    │ │ │ -         Reply = handle_msg(Msg),
    │ │ │ +      {sys, Msg} ->
    │ │ │ +         handle_sys_msg(Msg),
    │ │ │ +         loop();
    │ │ │ +      {From, Msg} ->
    │ │ │ +         Reply = handle_msg(Msg),
    │ │ │           From ! Reply,
    │ │ │ -         loop()
    │ │ │ +         loop()
    │ │ │   end.

    │ │ │ │ │ │ │ │ │ │ │ │ Initial Heap Size │ │ │

    │ │ │

    The default initial heap size of 233 words is quite conservative to support │ │ │ @@ -190,30 +190,30 @@ │ │ │ │ │ │ Fetching Received Messages │ │ │ │ │ │

    The cost of fetching a received message from the message queue depends on how │ │ │ complicated the receive expression is. A simple expression that matches any │ │ │ message is very cheap because it retrieves the first message in the message │ │ │ queue:

    DO

    receive
    │ │ │ -    Message -> handle_msg(Message)
    │ │ │ +    Message -> handle_msg(Message)
    │ │ │  end.

    However, this is not always convenient: we can receive a message that we do not │ │ │ know how to handle at this point, so it is common to only match the messages we │ │ │ expect:

    receive
    │ │ │ -    {Tag, Message} -> handle_msg(Message)
    │ │ │ +    {Tag, Message} -> handle_msg(Message)
    │ │ │  end.

    While this is convenient it means that the entire message queue must be searched │ │ │ until it finds a matching message. This is very expensive for processes with │ │ │ long message queues, so there is an optimization for the common case of │ │ │ -sending a request and waiting for a response shortly after:

    DO

    MRef = monitor(process, Process),
    │ │ │ -Process ! {self(), MRef, Request},
    │ │ │ +sending a request and waiting for a response shortly after:

    DO

    MRef = monitor(process, Process),
    │ │ │ +Process ! {self(), MRef, Request},
    │ │ │  receive
    │ │ │ -    {MRef, Reply} ->
    │ │ │ -        erlang:demonitor(MRef, [flush]),
    │ │ │ -        handle_reply(Reply);
    │ │ │ -    {'DOWN', MRef, _, _, Reason} ->
    │ │ │ -        handle_error(Reason)
    │ │ │ +    {MRef, Reply} ->
    │ │ │ +        erlang:demonitor(MRef, [flush]),
    │ │ │ +        handle_reply(Reply);
    │ │ │ +    {'DOWN', MRef, _, _, Reason} ->
    │ │ │ +        handle_error(Reason)
    │ │ │  end.

    Since the compiler knows that the reference created by │ │ │ monitor/2 cannot exist before the call (since it is a globally │ │ │ unique identifier), and that the receive only matches messages that contain │ │ │ said reference, it will tell the emulator to search only the messages that │ │ │ arrived after the call to monitor/2.

    The above is a simple example where one is guaranteed that the optimization │ │ │ will take, but what about more complicated code?

    │ │ │ │ │ │ @@ -229,101 +229,101 @@ │ │ │ efficiency_guide.erl:200: Warning: NOT OPTIMIZED: all clauses do not match a suitable reference │ │ │ efficiency_guide.erl:206: Warning: OPTIMIZED: reference used to mark a message queue position │ │ │ efficiency_guide.erl:208: Warning: OPTIMIZED: all clauses match reference created by monitor/2 at efficiency_guide.erl:206 │ │ │ efficiency_guide.erl:219: Warning: INFO: passing reference created by make_ref/0 at efficiency_guide.erl:218 │ │ │ efficiency_guide.erl:222: Warning: OPTIMIZED: all clauses match reference in function parameter 1

    To make it clearer exactly what code the warnings refer to, the warnings in the │ │ │ following examples are inserted as comments after the clause they refer to, for │ │ │ example:

    %% DO
    │ │ │ -simple_receive() ->
    │ │ │ +simple_receive() ->
    │ │ │  %% efficiency_guide.erl:194: Warning: INFO: not a selective receive, this is always fast
    │ │ │  receive
    │ │ │ -    Message -> handle_msg(Message)
    │ │ │ +    Message -> handle_msg(Message)
    │ │ │  end.
    │ │ │  
    │ │ │  %% DO NOT, unless Tag is known to be a suitable reference: see
    │ │ │  %% cross_function_receive/0 further down.
    │ │ │ -selective_receive(Tag, Message) ->
    │ │ │ +selective_receive(Tag, Message) ->
    │ │ │  %% efficiency_guide.erl:200: Warning: NOT OPTIMIZED: all clauses do not match a suitable reference
    │ │ │  receive
    │ │ │ -    {Tag, Message} -> handle_msg(Message)
    │ │ │ +    {Tag, Message} -> handle_msg(Message)
    │ │ │  end.
    │ │ │  
    │ │ │  %% DO
    │ │ │ -optimized_receive(Process, Request) ->
    │ │ │ +optimized_receive(Process, Request) ->
    │ │ │  %% efficiency_guide.erl:206: Warning: OPTIMIZED: reference used to mark a message queue position
    │ │ │ -    MRef = monitor(process, Process),
    │ │ │ -    Process ! {self(), MRef, Request},
    │ │ │ +    MRef = monitor(process, Process),
    │ │ │ +    Process ! {self(), MRef, Request},
    │ │ │      %% efficiency_guide.erl:208: Warning: OPTIMIZED: matches reference created by monitor/2 at efficiency_guide.erl:206
    │ │ │      receive
    │ │ │ -        {MRef, Reply} ->
    │ │ │ -        erlang:demonitor(MRef, [flush]),
    │ │ │ -        handle_reply(Reply);
    │ │ │ -    {'DOWN', MRef, _, _, Reason} ->
    │ │ │ -    handle_error(Reason)
    │ │ │ +        {MRef, Reply} ->
    │ │ │ +        erlang:demonitor(MRef, [flush]),
    │ │ │ +        handle_reply(Reply);
    │ │ │ +    {'DOWN', MRef, _, _, Reason} ->
    │ │ │ +    handle_error(Reason)
    │ │ │      end.
    │ │ │  
    │ │ │  %% DO
    │ │ │ -cross_function_receive() ->
    │ │ │ +cross_function_receive() ->
    │ │ │      %% efficiency_guide.erl:218: Warning: OPTIMIZED: reference used to mark a message queue position
    │ │ │ -    Ref = make_ref(),
    │ │ │ +    Ref = make_ref(),
    │ │ │      %% efficiency_guide.erl:219: Warning: INFO: passing reference created by make_ref/0 at efficiency_guide.erl:218
    │ │ │ -    cross_function_receive(Ref).
    │ │ │ +    cross_function_receive(Ref).
    │ │ │  
    │ │ │ -cross_function_receive(Ref) ->
    │ │ │ +cross_function_receive(Ref) ->
    │ │ │      %% efficiency_guide.erl:222: Warning: OPTIMIZED: all clauses match reference in function parameter 1
    │ │ │      receive
    │ │ │ -        {Ref, Message} -> handle_msg(Message)
    │ │ │ +        {Ref, Message} -> handle_msg(Message)
    │ │ │      end.

    │ │ │ │ │ │ │ │ │ │ │ │ Literal Pool │ │ │

    │ │ │

    Constant Erlang terms (hereafter called literals) are kept in literal pools; │ │ │ each loaded module has its own pool. The following function does not build the │ │ │ tuple every time it is called (only to have it discarded the next time the │ │ │ garbage collector was run), but the tuple is located in the module's literal │ │ │ -pool:

    DO

    days_in_month(M) ->
    │ │ │ -    element(M, {31,28,31,30,31,30,31,31,30,31,30,31}).

    If a literal, or a term that contains a literal, is inserted into an Ets table, │ │ │ +pool:

    DO

    days_in_month(M) ->
    │ │ │ +    element(M, {31,28,31,30,31,30,31,31,30,31,30,31}).

    If a literal, or a term that contains a literal, is inserted into an Ets table, │ │ │ it is copied. The reason is that the module containing the literal can be │ │ │ unloaded in the future.

    When a literal is sent to another process, it is not copied. When a module │ │ │ holding a literal is unloaded, the literal will be copied to the heap of all │ │ │ processes that hold references to that literal.

    There also exists a global literal pool that is managed by the │ │ │ persistent_term module.

    By default, 1 GB of virtual address space is reserved for all literal pools (in │ │ │ BEAM code and persistent terms). The amount of virtual address space reserved │ │ │ for literals can be changed by using the │ │ │ +MIscs option when starting the emulator.

    Here is an example how the reserved virtual address space for literals can be │ │ │ raised to 2 GB (2048 MB):

    erl +MIscs 2048

    │ │ │ │ │ │ │ │ │ │ │ │ Loss of Sharing │ │ │

    │ │ │ -

    An Erlang term can have shared subterms. Here is a simple example:

    {SubTerm, SubTerm}

    Shared subterms are not preserved in the following cases:

    • When a term is sent to another process
    • When a term is passed as the initial process arguments in the spawn call
    • When a term is stored in an Ets table

    That is an optimization. Most applications do not send messages with shared │ │ │ -subterms.

    The following example shows how a shared subterm can be created:

    kilo_byte() ->
    │ │ │ -    kilo_byte(10, [42]).
    │ │ │ +

    An Erlang term can have shared subterms. Here is a simple example:

    {SubTerm, SubTerm}

    Shared subterms are not preserved in the following cases:

    • When a term is sent to another process
    • When a term is passed as the initial process arguments in the spawn call
    • When a term is stored in an Ets table

    That is an optimization. Most applications do not send messages with shared │ │ │ +subterms.

    The following example shows how a shared subterm can be created:

    kilo_byte() ->
    │ │ │ +    kilo_byte(10, [42]).
    │ │ │  
    │ │ │ -kilo_byte(0, Acc) ->
    │ │ │ +kilo_byte(0, Acc) ->
    │ │ │      Acc;
    │ │ │ -kilo_byte(N, Acc) ->
    │ │ │ -    kilo_byte(N-1, [Acc|Acc]).

    kilo_byte/1 creates a deep list. If list_to_binary/1 │ │ │ +kilo_byte(N, Acc) -> │ │ │ + kilo_byte(N-1, [Acc|Acc]).

    kilo_byte/1 creates a deep list. If list_to_binary/1 │ │ │ is called, the deep list can be converted to a binary of 1024 bytes:

    1> byte_size(list_to_binary(efficiency_guide:kilo_byte())).
    │ │ │  1024

    Using the erts_debug:size/1 BIF, it can be seen that the deep list only │ │ │ -requires 22 words of heap space:

    2> erts_debug:size(efficiency_guide:kilo_byte()).
    │ │ │ +requires 22 words of heap space:

    2> erts_debug:size(efficiency_guide:kilo_byte()).
    │ │ │  22

    Using the erts_debug:flat_size/1 BIF, the size of the deep list can be │ │ │ calculated if sharing is ignored. It becomes the size of the list when it has │ │ │ -been sent to another process or stored in an Ets table:

    3> erts_debug:flat_size(efficiency_guide:kilo_byte()).
    │ │ │ +been sent to another process or stored in an Ets table:

    3> erts_debug:flat_size(efficiency_guide:kilo_byte()).
    │ │ │  4094

    It can be verified that sharing will be lost if the data is inserted into an Ets │ │ │ -table:

    4> T = ets:new(tab, []).
    │ │ │ +table:

    4> T = ets:new(tab, []).
    │ │ │  #Ref<0.1662103692.2407923716.214181>
    │ │ │ -5> ets:insert(T, {key,efficiency_guide:kilo_byte()}).
    │ │ │ +5> ets:insert(T, {key,efficiency_guide:kilo_byte()}).
    │ │ │  true
    │ │ │ -6> erts_debug:size(element(2, hd(ets:lookup(T, key)))).
    │ │ │ +6> erts_debug:size(element(2, hd(ets:lookup(T, key)))).
    │ │ │  4094
    │ │ │ -7> erts_debug:flat_size(element(2, hd(ets:lookup(T, key)))).
    │ │ │ +7> erts_debug:flat_size(element(2, hd(ets:lookup(T, key)))).
    │ │ │  4094

    When the data has passed through an Ets table, erts_debug:size/1 and │ │ │ erts_debug:flat_size/1 return the same value. Sharing has been lost.

    It is possible to build an experimental variant of the runtime system that │ │ │ will preserve sharing when copying terms by giving the │ │ │ --enable-sharing-preserving option to the configure script.

    │ │ │ │ │ │ │ │ │ │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/erl_interface.html │ │ │ @@ -120,119 +120,119 @@ │ │ │ to read the port example in Ports before reading this section.

    │ │ │ │ │ │ │ │ │ │ │ │ Erlang Program │ │ │

    │ │ │

    The following example shows an Erlang program communicating with a C program │ │ │ -over a plain port with home made encoding:

    -module(complex1).
    │ │ │ --export([start/1, stop/0, init/1]).
    │ │ │ --export([foo/1, bar/1]).
    │ │ │ -
    │ │ │ -start(ExtPrg) ->
    │ │ │ -    spawn(?MODULE, init, [ExtPrg]).
    │ │ │ -stop() ->
    │ │ │ +over a plain port with home made encoding:

    -module(complex1).
    │ │ │ +-export([start/1, stop/0, init/1]).
    │ │ │ +-export([foo/1, bar/1]).
    │ │ │ +
    │ │ │ +start(ExtPrg) ->
    │ │ │ +    spawn(?MODULE, init, [ExtPrg]).
    │ │ │ +stop() ->
    │ │ │      complex ! stop.
    │ │ │  
    │ │ │ -foo(X) ->
    │ │ │ -    call_port({foo, X}).
    │ │ │ -bar(Y) ->
    │ │ │ -    call_port({bar, Y}).
    │ │ │ +foo(X) ->
    │ │ │ +    call_port({foo, X}).
    │ │ │ +bar(Y) ->
    │ │ │ +    call_port({bar, Y}).
    │ │ │  
    │ │ │ -call_port(Msg) ->
    │ │ │ -    complex ! {call, self(), Msg},
    │ │ │ +call_port(Msg) ->
    │ │ │ +    complex ! {call, self(), Msg},
    │ │ │      receive
    │ │ │ -	{complex, Result} ->
    │ │ │ +	{complex, Result} ->
    │ │ │  	    Result
    │ │ │      end.
    │ │ │  
    │ │ │ -init(ExtPrg) ->
    │ │ │ -    register(complex, self()),
    │ │ │ -    process_flag(trap_exit, true),
    │ │ │ -    Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
    │ │ │ -    loop(Port).
    │ │ │ +init(ExtPrg) ->
    │ │ │ +    register(complex, self()),
    │ │ │ +    process_flag(trap_exit, true),
    │ │ │ +    Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
    │ │ │ +    loop(Port).
    │ │ │  
    │ │ │ -loop(Port) ->
    │ │ │ +loop(Port) ->
    │ │ │      receive
    │ │ │ -	{call, Caller, Msg} ->
    │ │ │ -	    Port ! {self(), {command, encode(Msg)}},
    │ │ │ +	{call, Caller, Msg} ->
    │ │ │ +	    Port ! {self(), {command, encode(Msg)}},
    │ │ │  	    receive
    │ │ │ -		{Port, {data, Data}} ->
    │ │ │ -		    Caller ! {complex, decode(Data)}
    │ │ │ +		{Port, {data, Data}} ->
    │ │ │ +		    Caller ! {complex, decode(Data)}
    │ │ │  	    end,
    │ │ │ -	    loop(Port);
    │ │ │ +	    loop(Port);
    │ │ │  	stop ->
    │ │ │ -	    Port ! {self(), close},
    │ │ │ +	    Port ! {self(), close},
    │ │ │  	    receive
    │ │ │ -		{Port, closed} ->
    │ │ │ -		    exit(normal)
    │ │ │ +		{Port, closed} ->
    │ │ │ +		    exit(normal)
    │ │ │  	    end;
    │ │ │ -	{'EXIT', Port, Reason} ->
    │ │ │ -	    exit(port_terminated)
    │ │ │ +	{'EXIT', Port, Reason} ->
    │ │ │ +	    exit(port_terminated)
    │ │ │      end.
    │ │ │  
    │ │ │ -encode({foo, X}) -> [1, X];
    │ │ │ -encode({bar, Y}) -> [2, Y].
    │ │ │ +encode({foo, X}) -> [1, X];
    │ │ │ +encode({bar, Y}) -> [2, Y].
    │ │ │  
    │ │ │ -decode([Int]) -> Int.

    There are two differences when using Erl_Interface on the C side compared to the │ │ │ +decode([Int]) -> Int.

    There are two differences when using Erl_Interface on the C side compared to the │ │ │ example in Ports, using only the plain port:

    • As Erl_Interface operates on the Erlang external term format, the port must be │ │ │ set to use binaries.
    • Instead of inventing an encoding/decoding scheme, the │ │ │ term_to_binary/1 and │ │ │ -binary_to_term/1 BIFs are to be used.

    That is:

    open_port({spawn, ExtPrg}, [{packet, 2}])

    is replaced with:

    open_port({spawn, ExtPrg}, [{packet, 2}, binary])

    And:

    Port ! {self(), {command, encode(Msg)}},
    │ │ │ +binary_to_term/1 BIFs are to be used.

    That is:

    open_port({spawn, ExtPrg}, [{packet, 2}])

    is replaced with:

    open_port({spawn, ExtPrg}, [{packet, 2}, binary])

    And:

    Port ! {self(), {command, encode(Msg)}},
    │ │ │  receive
    │ │ │ -  {Port, {data, Data}} ->
    │ │ │ -    Caller ! {complex, decode(Data)}
    │ │ │ -end

    is replaced with:

    Port ! {self(), {command, term_to_binary(Msg)}},
    │ │ │ +  {Port, {data, Data}} ->
    │ │ │ +    Caller ! {complex, decode(Data)}
    │ │ │ +end

    is replaced with:

    Port ! {self(), {command, term_to_binary(Msg)}},
    │ │ │  receive
    │ │ │ -  {Port, {data, Data}} ->
    │ │ │ -    Caller ! {complex, binary_to_term(Data)}
    │ │ │ -end

    The resulting Erlang program is as follows:

    -module(complex2).
    │ │ │ --export([start/1, stop/0, init/1]).
    │ │ │ --export([foo/1, bar/1]).
    │ │ │ -
    │ │ │ -start(ExtPrg) ->
    │ │ │ -    spawn(?MODULE, init, [ExtPrg]).
    │ │ │ -stop() ->
    │ │ │ +  {Port, {data, Data}} ->
    │ │ │ +    Caller ! {complex, binary_to_term(Data)}
    │ │ │ +end

    The resulting Erlang program is as follows:

    -module(complex2).
    │ │ │ +-export([start/1, stop/0, init/1]).
    │ │ │ +-export([foo/1, bar/1]).
    │ │ │ +
    │ │ │ +start(ExtPrg) ->
    │ │ │ +    spawn(?MODULE, init, [ExtPrg]).
    │ │ │ +stop() ->
    │ │ │      complex ! stop.
    │ │ │  
    │ │ │ -foo(X) ->
    │ │ │ -    call_port({foo, X}).
    │ │ │ -bar(Y) ->
    │ │ │ -    call_port({bar, Y}).
    │ │ │ +foo(X) ->
    │ │ │ +    call_port({foo, X}).
    │ │ │ +bar(Y) ->
    │ │ │ +    call_port({bar, Y}).
    │ │ │  
    │ │ │ -call_port(Msg) ->
    │ │ │ -    complex ! {call, self(), Msg},
    │ │ │ +call_port(Msg) ->
    │ │ │ +    complex ! {call, self(), Msg},
    │ │ │      receive
    │ │ │ -	{complex, Result} ->
    │ │ │ +	{complex, Result} ->
    │ │ │  	    Result
    │ │ │      end.
    │ │ │  
    │ │ │ -init(ExtPrg) ->
    │ │ │ -    register(complex, self()),
    │ │ │ -    process_flag(trap_exit, true),
    │ │ │ -    Port = open_port({spawn, ExtPrg}, [{packet, 2}, binary]),
    │ │ │ -    loop(Port).
    │ │ │ +init(ExtPrg) ->
    │ │ │ +    register(complex, self()),
    │ │ │ +    process_flag(trap_exit, true),
    │ │ │ +    Port = open_port({spawn, ExtPrg}, [{packet, 2}, binary]),
    │ │ │ +    loop(Port).
    │ │ │  
    │ │ │ -loop(Port) ->
    │ │ │ +loop(Port) ->
    │ │ │      receive
    │ │ │ -	{call, Caller, Msg} ->
    │ │ │ -	    Port ! {self(), {command, term_to_binary(Msg)}},
    │ │ │ +	{call, Caller, Msg} ->
    │ │ │ +	    Port ! {self(), {command, term_to_binary(Msg)}},
    │ │ │  	    receive
    │ │ │ -		{Port, {data, Data}} ->
    │ │ │ -		    Caller ! {complex, binary_to_term(Data)}
    │ │ │ +		{Port, {data, Data}} ->
    │ │ │ +		    Caller ! {complex, binary_to_term(Data)}
    │ │ │  	    end,
    │ │ │ -	    loop(Port);
    │ │ │ +	    loop(Port);
    │ │ │  	stop ->
    │ │ │ -	    Port ! {self(), close},
    │ │ │ +	    Port ! {self(), close},
    │ │ │  	    receive
    │ │ │ -		{Port, closed} ->
    │ │ │ -		    exit(normal)
    │ │ │ +		{Port, closed} ->
    │ │ │ +		    exit(normal)
    │ │ │  	    end;
    │ │ │ -	{'EXIT', Port, Reason} ->
    │ │ │ -	    exit(port_terminated)
    │ │ │ +	{'EXIT', Port, Reason} ->
    │ │ │ +	    exit(port_terminated)
    │ │ │      end.

    Notice that calling complex2:foo/1 and complex2:bar/1 results in the tuple │ │ │ {foo,X} or {bar,Y} being sent to the complex process, which codes them as │ │ │ binaries and sends them to the port. This means that the C program must be able │ │ │ to handle these two tuples.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -362,27 +362,27 @@ │ │ │ -L/usr/local/otp/lib/erl_interface-3.9.2/lib \ │ │ │ complex.c erl_comm.c ei.c -lei -lpthread

    In Erlang/OTP R5B and later versions of OTP, the include and lib directories │ │ │ are situated under $OTPROOT/lib/erl_interface-VSN, where $OTPROOT is the │ │ │ root directory of the OTP installation (/usr/local/otp in the recent example) │ │ │ and VSN is the version of the Erl_interface application (3.2.1 in the recent │ │ │ example).

    In R4B and earlier versions of OTP, include and lib are situated under │ │ │ $OTPROOT/usr.

    Step 2. Start Erlang and compile the Erlang code:

    $ erl
    │ │ │ -Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
    │ │ │ +Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
    │ │ │  
    │ │ │ -Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
    │ │ │ -1> c(complex2).
    │ │ │ -{ok,complex2}

    Step 3. Run the example:

    2> complex2:start("./extprg").
    │ │ │ +Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
    │ │ │ +1> c(complex2).
    │ │ │ +{ok,complex2}

    Step 3. Run the example:

    2> complex2:start("./extprg").
    │ │ │  <0.34.0>
    │ │ │ -3> complex2:foo(3).
    │ │ │ +3> complex2:foo(3).
    │ │ │  4
    │ │ │ -4> complex2:bar(5).
    │ │ │ +4> complex2:bar(5).
    │ │ │  10
    │ │ │ -5> complex2:bar(352).
    │ │ │ +5> complex2:bar(352).
    │ │ │  704
    │ │ │ -6> complex2:stop().
    │ │ │ +6> complex2:stop().
    │ │ │  stop
    │ │ │ │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │

    logger_sasl_compatible to │ │ │ true. For more information, see │ │ │ SASL Error Logging in the SASL User's Guide.

    % erl -kernel logger_level info
    │ │ │ -Erlang/OTP 21 [erts-10.0] [source-13c50db] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]
    │ │ │ +Erlang/OTP 21 [erts-10.0] [source-13c50db] [64-bit] [smp:4:4] [ds:4:4:10] [async-threads:1] [hipe]
    │ │ │  
    │ │ │  =PROGRESS REPORT==== 8-Jun-2018::16:54:19.916404 ===
    │ │ │      application: kernel
    │ │ │      started_at: nonode@nohost
    │ │ │  =PROGRESS REPORT==== 8-Jun-2018::16:54:19.922908 ===
    │ │ │      application: stdlib
    │ │ │      started_at: nonode@nohost
    │ │ │  =PROGRESS REPORT==== 8-Jun-2018::16:54:19.925755 ===
    │ │ │ -    supervisor: {local,kernel_safe_sup}
    │ │ │ -    started: [{pid,<0.74.0>},
    │ │ │ -              {id,disk_log_sup},
    │ │ │ -              {mfargs,{disk_log_sup,start_link,[]}},
    │ │ │ -              {restart_type,permanent},
    │ │ │ -              {shutdown,1000},
    │ │ │ -              {child_type,supervisor}]
    │ │ │ +    supervisor: {local,kernel_safe_sup}
    │ │ │ +    started: [{pid,<0.74.0>},
    │ │ │ +              {id,disk_log_sup},
    │ │ │ +              {mfargs,{disk_log_sup,start_link,[]}},
    │ │ │ +              {restart_type,permanent},
    │ │ │ +              {shutdown,1000},
    │ │ │ +              {child_type,supervisor}]
    │ │ │  =PROGRESS REPORT==== 8-Jun-2018::16:54:19.926056 ===
    │ │ │ -    supervisor: {local,kernel_safe_sup}
    │ │ │ -    started: [{pid,<0.75.0>},
    │ │ │ -              {id,disk_log_server},
    │ │ │ -              {mfargs,{disk_log_server,start_link,[]}},
    │ │ │ -              {restart_type,permanent},
    │ │ │ -              {shutdown,2000},
    │ │ │ -              {child_type,worker}]
    │ │ │ -Eshell V10.0  (abort with ^G)
    │ │ │ +    supervisor: {local,kernel_safe_sup}
    │ │ │ +    started: [{pid,<0.75.0>},
    │ │ │ +              {id,disk_log_server},
    │ │ │ +              {mfargs,{disk_log_server,start_link,[]}},
    │ │ │ +              {restart_type,permanent},
    │ │ │ +              {shutdown,2000},
    │ │ │ +              {child_type,worker}]
    │ │ │ +Eshell V10.0  (abort with ^G)
    │ │ │  1>
    │ │ │
    │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │ try expression can │ │ │ distinguish between the different classes, whereas the │ │ │ catch expression cannot. try and catch are described │ │ │ in Expressions.

    ClassOrigin
    errorRun-time error, for example, 1+a, or the process called error/1
    exitThe process called exit/1
    throwThe process called throw/1

    Table: Exception Classes.

    All of the above exceptions can also be generated by calling erlang:raise/3.

    An exception consists of its class, an exit reason (see │ │ │ Exit Reason), and a stack trace (which aids in finding │ │ │ the code location of the exception).

    The stack trace can be bound to a variable from within a try expression for │ │ │ any exception class, or as part of the exit reason when a run-time error is │ │ │ -caught by a catch. Example:

    > {'EXIT',{test,Stacktrace}} = (catch error(test)), Stacktrace.
    │ │ │ -[{shell,apply_fun,3,[]},
    │ │ │ - {erl_eval,do_apply,6,[]},
    │ │ │ - ...]
    │ │ │ -> try throw(test) catch Class:Reason:Stacktrace -> Stacktrace end.
    │ │ │ -[{shell,apply_fun,3,[]},
    │ │ │ - {erl_eval,do_apply,6,[]},
    │ │ │ - ...]

    │ │ │ +caught by a catch. Example:

    > {'EXIT',{test,Stacktrace}} = (catch error(test)), Stacktrace.
    │ │ │ +[{shell,apply_fun,3,[]},
    │ │ │ + {erl_eval,do_apply,6,[]},
    │ │ │ + ...]
    │ │ │ +> try throw(test) catch Class:Reason:Stacktrace -> Stacktrace end.
    │ │ │ +[{shell,apply_fun,3,[]},
    │ │ │ + {erl_eval,do_apply,6,[]},
    │ │ │ + ...]

    │ │ │ │ │ │ │ │ │ │ │ │ The call-stack back trace (stacktrace) │ │ │

    │ │ │

    The stack back-trace (stacktrace) is a list that │ │ │ contains {Module, Function, Arity, ExtraInfo} and/or {Fun, Arity, ExtraInfo} │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/events.html │ │ │ @@ -135,43 +135,43 @@ │ │ │ event handler.

    │ │ │ │ │ │ │ │ │ │ │ │ Example │ │ │

    │ │ │

    The callback module for the event handler writing error messages to the terminal │ │ │ -can look as follows:

    -module(terminal_logger).
    │ │ │ --behaviour(gen_event).
    │ │ │ +can look as follows:

    -module(terminal_logger).
    │ │ │ +-behaviour(gen_event).
    │ │ │  
    │ │ │ --export([init/1, handle_event/2, terminate/2]).
    │ │ │ +-export([init/1, handle_event/2, terminate/2]).
    │ │ │  
    │ │ │ -init(_Args) ->
    │ │ │ -    {ok, []}.
    │ │ │ +init(_Args) ->
    │ │ │ +    {ok, []}.
    │ │ │  
    │ │ │ -handle_event(ErrorMsg, State) ->
    │ │ │ -    io:format("***Error*** ~p~n", [ErrorMsg]),
    │ │ │ -    {ok, State}.
    │ │ │ +handle_event(ErrorMsg, State) ->
    │ │ │ +    io:format("***Error*** ~p~n", [ErrorMsg]),
    │ │ │ +    {ok, State}.
    │ │ │  
    │ │ │ -terminate(_Args, _State) ->
    │ │ │ +terminate(_Args, _State) ->
    │ │ │      ok.

    The callback module for the event handler writing error messages to a file can │ │ │ -look as follows:

    -module(file_logger).
    │ │ │ --behaviour(gen_event).
    │ │ │ +look as follows:

    -module(file_logger).
    │ │ │ +-behaviour(gen_event).
    │ │ │  
    │ │ │ --export([init/1, handle_event/2, terminate/2]).
    │ │ │ +-export([init/1, handle_event/2, terminate/2]).
    │ │ │  
    │ │ │ -init(File) ->
    │ │ │ -    {ok, Fd} = file:open(File, read),
    │ │ │ -    {ok, Fd}.
    │ │ │ -
    │ │ │ -handle_event(ErrorMsg, Fd) ->
    │ │ │ -    io:format(Fd, "***Error*** ~p~n", [ErrorMsg]),
    │ │ │ -    {ok, Fd}.
    │ │ │ +init(File) ->
    │ │ │ +    {ok, Fd} = file:open(File, read),
    │ │ │ +    {ok, Fd}.
    │ │ │ +
    │ │ │ +handle_event(ErrorMsg, Fd) ->
    │ │ │ +    io:format(Fd, "***Error*** ~p~n", [ErrorMsg]),
    │ │ │ +    {ok, Fd}.
    │ │ │  
    │ │ │ -terminate(_Args, Fd) ->
    │ │ │ -    file:close(Fd).

    The code is explained in the next sections.

    │ │ │ +terminate(_Args, Fd) -> │ │ │ + file:close(Fd).

    The code is explained in the next sections.

    │ │ │ │ │ │ │ │ │ │ │ │ Starting an Event Manager │ │ │

    │ │ │

    To start an event manager for handling errors, as described in the previous │ │ │ example, call the following function:

    gen_event:start_link({local, error_man})

    gen_event:start_link/1 spawns and links to a new event manager process.

    The argument, {local, error_man}, specifies the name under which the │ │ │ @@ -184,57 +184,57 @@ │ │ │ manager that is not part of a supervision tree.

    │ │ │ │ │ │ │ │ │ │ │ │ Adding an Event Handler │ │ │

    │ │ │

    The following example shows how to start an event manager and add an event │ │ │ -handler to it by using the shell:

    1> gen_event:start({local, error_man}).
    │ │ │ -{ok,<0.31.0>}
    │ │ │ -2> gen_event:add_handler(error_man, terminal_logger, []).
    │ │ │ +handler to it by using the shell:

    1> gen_event:start({local, error_man}).
    │ │ │ +{ok,<0.31.0>}
    │ │ │ +2> gen_event:add_handler(error_man, terminal_logger, []).
    │ │ │  ok

    This function sends a message to the event manager registered as error_man, │ │ │ telling it to add the event handler terminal_logger. The event manager calls │ │ │ the callback function terminal_logger:init([]), where the argument [] is the │ │ │ third argument to add_handler. init/1 is expected to return {ok, State}, │ │ │ -where State is the internal state of the event handler.

    init(_Args) ->
    │ │ │ -    {ok, []}.

    Here, init/1 does not need any input data and ignores its argument. For │ │ │ +where State is the internal state of the event handler.

    init(_Args) ->
    │ │ │ +    {ok, []}.

    Here, init/1 does not need any input data and ignores its argument. For │ │ │ terminal_logger, the internal state is not used. For file_logger, the │ │ │ -internal state is used to save the open file descriptor.

    init(File) ->
    │ │ │ -    {ok, Fd} = file:open(File, read),
    │ │ │ -    {ok, Fd}.

    │ │ │ +internal state is used to save the open file descriptor.

    init(File) ->
    │ │ │ +    {ok, Fd} = file:open(File, read),
    │ │ │ +    {ok, Fd}.

    │ │ │ │ │ │ │ │ │ │ │ │ Notifying about Events │ │ │

    │ │ │
    3> gen_event:notify(error_man, no_reply).
    │ │ │  ***Error*** no_reply
    │ │ │  ok

    error_man is the name of the event manager and no_reply is the event.

    The event is made into a message and sent to the event manager. When the event │ │ │ is received, the event manager calls handle_event(Event, State) for each │ │ │ installed event handler, in the same order as they were added. The function is │ │ │ expected to return a tuple {ok,State1}, where State1 is a new value for the │ │ │ -state of the event handler.

    In terminal_logger:

    handle_event(ErrorMsg, State) ->
    │ │ │ -    io:format("***Error*** ~p~n", [ErrorMsg]),
    │ │ │ -    {ok, State}.

    In file_logger:

    handle_event(ErrorMsg, Fd) ->
    │ │ │ -    io:format(Fd, "***Error*** ~p~n", [ErrorMsg]),
    │ │ │ -    {ok, Fd}.

    │ │ │ +state of the event handler.

    In terminal_logger:

    handle_event(ErrorMsg, State) ->
    │ │ │ +    io:format("***Error*** ~p~n", [ErrorMsg]),
    │ │ │ +    {ok, State}.

    In file_logger:

    handle_event(ErrorMsg, Fd) ->
    │ │ │ +    io:format(Fd, "***Error*** ~p~n", [ErrorMsg]),
    │ │ │ +    {ok, Fd}.

    │ │ │ │ │ │ │ │ │ │ │ │ Deleting an Event Handler │ │ │

    │ │ │ -
    4> gen_event:delete_handler(error_man, terminal_logger, []).
    │ │ │ +
    4> gen_event:delete_handler(error_man, terminal_logger, []).
    │ │ │  ok

    This function sends a message to the event manager registered as error_man, │ │ │ telling it to delete the event handler terminal_logger. The event manager │ │ │ calls the callback function terminal_logger:terminate([], State), where the │ │ │ argument [] is the third argument to delete_handler. terminate/2 is to be │ │ │ the opposite of init/1 and do any necessary cleaning up. Its return value is │ │ │ -ignored.

    For terminal_logger, no cleaning up is necessary:

    terminate(_Args, _State) ->
    │ │ │ -    ok.

    For file_logger, the file descriptor opened in init must be closed:

    terminate(_Args, Fd) ->
    │ │ │ -    file:close(Fd).

    │ │ │ +ignored.

    For terminal_logger, no cleaning up is necessary:

    terminate(_Args, _State) ->
    │ │ │ +    ok.

    For file_logger, the file descriptor opened in init must be closed:

    terminate(_Args, Fd) ->
    │ │ │ +    file:close(Fd).

    │ │ │ │ │ │ │ │ │ │ │ │ Stopping │ │ │

    │ │ │

    When an event manager is stopped, it gives each of the installed event handlers │ │ │ the chance to clean up by calling terminate/2, the same way as when deleting a │ │ │ @@ -249,33 +249,33 @@ │ │ │ this is done is defined by a shutdown strategy set in │ │ │ the supervisor.

    │ │ │ │ │ │ │ │ │ │ │ │ Standalone Event Managers │ │ │

    │ │ │ -

    An event manager can also be stopped by calling:

    1> gen_event:stop(error_man).
    │ │ │ +

    An event manager can also be stopped by calling:

    1> gen_event:stop(error_man).
    │ │ │  ok

    │ │ │ │ │ │ │ │ │ │ │ │ Handling Other Messages │ │ │

    │ │ │

    If the gen_event process is to be able to receive other messages │ │ │ than events, the callback function handle_info(Info, State) must be │ │ │ implemented to handle them. Examples of other messages are exit │ │ │ messages if the event manager is linked to other processes than the │ │ │ supervisor (for example via gen_event:add_sup_handler/3) and is │ │ │ -trapping exit signals.

    handle_info({'EXIT', Pid, Reason}, State) ->
    │ │ │ +trapping exit signals.

    handle_info({'EXIT', Pid, Reason}, State) ->
    │ │ │      %% Code to handle exits here.
    │ │ │      ...
    │ │ │ -    {noreply, State1}.

    The final function to implement is code_change/3:

    code_change(OldVsn, State, Extra) ->
    │ │ │ +    {noreply, State1}.

    The final function to implement is code_change/3:

    code_change(OldVsn, State, Extra) ->
    │ │ │      %% Code to convert state (and more) during code change.
    │ │ │      ...
    │ │ │ -    {ok, NewState}.
    │ │ │ +
    {ok, NewState}.
    │ │ │
    │ │ │ │ │ │

    pattern matching. Erlang uses │ │ │ single assignment, that is, a variable can only be bound once.

    The anonymous variable is denoted by underscore (_) and can be used when a │ │ │ variable is required but its value can be ignored.

    Example:

    [H|_] = [1,2,3]

    Variables starting with underscore (_), for example, _Height, are normal │ │ │ variables, not anonymous. However, they are ignored by the compiler in the sense │ │ │ -that they do not generate warnings.

    Example:

    The following code:

    member(_, []) ->
    │ │ │ -    [].

    can be rewritten to be more readable:

    member(Elem, []) ->
    │ │ │ -    [].

    This causes a warning for an unused variable, Elem. To avoid the warning, │ │ │ -the code can be rewritten to:

    member(_Elem, []) ->
    │ │ │ -    [].

    Notice that since variables starting with an underscore are not anonymous, the │ │ │ -following example matches:

    {_,_} = {1,2}

    But this example fails:

    {_N,_N} = {1,2}

    The scope for a variable is its function clause. Variables bound in a branch of │ │ │ +that they do not generate warnings.

    Example:

    The following code:

    member(_, []) ->
    │ │ │ +    [].

    can be rewritten to be more readable:

    member(Elem, []) ->
    │ │ │ +    [].

    This causes a warning for an unused variable, Elem. To avoid the warning, │ │ │ +the code can be rewritten to:

    member(_Elem, []) ->
    │ │ │ +    [].

    Notice that since variables starting with an underscore are not anonymous, the │ │ │ +following example matches:

    {_,_} = {1,2}

    But this example fails:

    {_N,_N} = {1,2}

    The scope for a variable is its function clause. Variables bound in a branch of │ │ │ an if, case, or receive expression must be bound in all branches to have a │ │ │ value outside the expression. Otherwise they are regarded as unsafe outside │ │ │ the expression.

    For the try expression variable scoping is limited so that variables bound in │ │ │ the expression are always unsafe outside the expression.

    │ │ │ │ │ │ │ │ │ │ │ │ Patterns │ │ │

    │ │ │

    A pattern has the same structure as a term but can contain unbound variables.

    Example:

    Name1
    │ │ │ -[H|T]
    │ │ │ -{error,Reason}

    Patterns are allowed in clause heads, case expressions, │ │ │ +[H|T] │ │ │ +{error,Reason}

    Patterns are allowed in clause heads, case expressions, │ │ │ receive expressions, and │ │ │ match expressions.

    │ │ │ │ │ │ │ │ │ │ │ │ The Compound Pattern Operator │ │ │

    │ │ │

    If Pattern1 and Pattern2 are valid patterns, the following is also a valid │ │ │ pattern:

    Pattern1 = Pattern2

    When matched against a term, both Pattern1 and Pattern2 are matched against │ │ │ -the term. The idea behind this feature is to avoid reconstruction of terms.

    Example:

    f({connect,From,To,Number,Options}, To) ->
    │ │ │ -    Signal = {connect,From,To,Number,Options},
    │ │ │ +the term. The idea behind this feature is to avoid reconstruction of terms.

    Example:

    f({connect,From,To,Number,Options}, To) ->
    │ │ │ +    Signal = {connect,From,To,Number,Options},
    │ │ │      ...;
    │ │ │ -f(Signal, To) ->
    │ │ │ -    ignore.

    can instead be written as

    f({connect,_,To,_,_} = Signal, To) ->
    │ │ │ +f(Signal, To) ->
    │ │ │ +    ignore.

    can instead be written as

    f({connect,_,To,_,_} = Signal, To) ->
    │ │ │      ...;
    │ │ │ -f(Signal, To) ->
    │ │ │ +f(Signal, To) ->
    │ │ │      ignore.

    The compound pattern operator does not imply that its operands are matched in │ │ │ any particular order. That means that it is not legal to bind a variable in │ │ │ Pattern1 and use it in Pattern2, or vice versa.

    │ │ │ │ │ │ │ │ │ │ │ │ String Prefix in Patterns │ │ │

    │ │ │ -

    When matching strings, the following is a valid pattern:

    f("prefix" ++ Str) -> ...

    This is syntactic sugar for the equivalent, but harder to read:

    f([$p,$r,$e,$f,$i,$x | Str]) -> ...

    │ │ │ +

    When matching strings, the following is a valid pattern:

    f("prefix" ++ Str) -> ...

    This is syntactic sugar for the equivalent, but harder to read:

    f([$p,$r,$e,$f,$i,$x | Str]) -> ...

    │ │ │ │ │ │ │ │ │ │ │ │ Expressions in Patterns │ │ │

    │ │ │

    An arithmetic expression can be used within a pattern if it meets both of the │ │ │ -following two conditions:

    • It uses only numeric or bitwise operators.
    • Its value can be evaluated to a constant when complied.

    Example:

    case {Value, Result} of
    │ │ │ -    {?THRESHOLD+1, ok} -> ...

    │ │ │ +following two conditions:

    • It uses only numeric or bitwise operators.
    • Its value can be evaluated to a constant when complied.

    Example:

    case {Value, Result} of
    │ │ │ +    {?THRESHOLD+1, ok} -> ...

    │ │ │ │ │ │ │ │ │ │ │ │ The Match Operator │ │ │

    │ │ │

    The following matches Pattern against Expr:

    Pattern = Expr

    If the matching succeeds, any unbound variable in the pattern becomes bound and │ │ │ the value of Expr is returned.

    If multiple match operators are applied in sequence, they will be evaluated from │ │ │ -right to left.

    If the matching fails, a badmatch run-time error occurs.

    Examples:

    1> {A, B} = T = {answer, 42}.
    │ │ │ -{answer,42}
    │ │ │ +right to left.

    If the matching fails, a badmatch run-time error occurs.

    Examples:

    1> {A, B} = T = {answer, 42}.
    │ │ │ +{answer,42}
    │ │ │  2> A.
    │ │ │  answer
    │ │ │  3> B.
    │ │ │  42
    │ │ │  4> T.
    │ │ │ -{answer,42}
    │ │ │ -5> {C, D} = [1, 2].
    │ │ │ +{answer,42}
    │ │ │ +5> {C, D} = [1, 2].
    │ │ │  ** exception error: no match of right-hand side value [1,2]

    Because multiple match operators are evaluated from right to left, it means │ │ │ that:

    Pattern1 = Pattern2 = . . . = PatternN = Expression

    is equivalent to:

    Temporary = Expression,
    │ │ │  PatternN = Temporary,
    │ │ │     .
    │ │ │     .
    │ │ │     .,
    │ │ │  Pattern2 = Temporary,
    │ │ │ @@ -239,30 +239,30 @@
    │ │ │  can safely be skipped on a first reading.

    The = character is used to denote two similar but distinct operators: the │ │ │ match operator and the compound pattern operator. Which one is meant is │ │ │ determined by context.

    The compound pattern operator is used to construct a compound pattern from two │ │ │ patterns. Compound patterns are accepted everywhere a pattern is accepted. A │ │ │ compound pattern matches if all of its constituent patterns match. It is not │ │ │ legal for a pattern that is part of a compound pattern to use variables (as keys │ │ │ in map patterns or sizes in binary patterns) bound in other sub patterns of the │ │ │ -same compound pattern.

    Examples:

    1> fun(#{Key := Value} = #{key := Key}) -> Value end.
    │ │ │ +same compound pattern.

    Examples:

    1> fun(#{Key := Value} = #{key := Key}) -> Value end.
    │ │ │  * 1:7: variable 'Key' is unbound
    │ │ │ -2> F = fun({A, B} = E) -> {E, A + B} end, F({1,2}).
    │ │ │ -{{1,2},3}
    │ │ │ -3> G = fun(<<A:8,B:8>> = <<C:16>>) -> {A, B, C} end, G(<<42,43>>).
    │ │ │ -{42,43,10795}

    The match operator is allowed everywhere an expression is allowed. It is used │ │ │ +2> F = fun({A, B} = E) -> {E, A + B} end, F({1,2}). │ │ │ +{{1,2},3} │ │ │ +3> G = fun(<<A:8,B:8>> = <<C:16>>) -> {A, B, C} end, G(<<42,43>>). │ │ │ +{42,43,10795}

    The match operator is allowed everywhere an expression is allowed. It is used │ │ │ to match the value of an expression to a pattern. If multiple match operators │ │ │ -are applied in sequence, they will be evaluated from right to left.

    Examples:

    1> M = #{key => key2, key2 => value}.
    │ │ │ -#{key => key2,key2 => value}
    │ │ │ -2> f(Key), #{Key := Value} = #{key := Key} = M, Value.
    │ │ │ +are applied in sequence, they will be evaluated from right to left.

    Examples:

    1> M = #{key => key2, key2 => value}.
    │ │ │ +#{key => key2,key2 => value}
    │ │ │ +2> f(Key), #{Key := Value} = #{key := Key} = M, Value.
    │ │ │  value
    │ │ │ -3> f(Key), #{Key := Value} = (#{key := Key} = M), Value.
    │ │ │ +3> f(Key), #{Key := Value} = (#{key := Key} = M), Value.
    │ │ │  value
    │ │ │ -4> f(Key), (#{Key := Value} = #{key := Key}) = M, Value.
    │ │ │ +4> f(Key), (#{Key := Value} = #{key := Key}) = M, Value.
    │ │ │  * 1:12: variable 'Key' is unbound
    │ │ │ -5> <<X:Y>> = begin Y = 8, <<42:8>> end, X.
    │ │ │ +5> <<X:Y>> = begin Y = 8, <<42:8>> end, X.
    │ │ │  42

    The expression at prompt 2> first matches the value of variable M against │ │ │ pattern #{key := Key}, binding variable Key. It then matches the value of │ │ │ M against pattern #{Key := Value} using variable Key as the key, binding │ │ │ variable Value.

    The expression at prompt 3> matches expression (#{key := Key} = M) against │ │ │ pattern #{Key := Value}. The expression inside the parentheses is evaluated │ │ │ first. That is, M is matched against #{key := Key}, and then the value of │ │ │ M is matched against pattern #{Key := Value}. That is the same evaluation │ │ │ @@ -276,30 +276,30 @@ │ │ │ binding variable Y and creating a binary. The binary is then matched against │ │ │ pattern <<X:Y>> using the value of Y as the size of the segment.

    │ │ │ │ │ │ │ │ │ │ │ │ Function Calls │ │ │

    │ │ │ -
    ExprF(Expr1,...,ExprN)
    │ │ │ -ExprM:ExprF(Expr1,...,ExprN)

    In the first form of function calls, ExprM:ExprF(Expr1,...,ExprN), each of │ │ │ +

    ExprF(Expr1,...,ExprN)
    │ │ │ +ExprM:ExprF(Expr1,...,ExprN)

    In the first form of function calls, ExprM:ExprF(Expr1,...,ExprN), each of │ │ │ ExprM and ExprF must be an atom or an expression that evaluates to an atom. │ │ │ The function is said to be called by using the fully qualified function name. │ │ │ -This is often referred to as a remote or external function call.

    Example:

    lists:keyfind(Name, 1, List)

    In the second form of function calls, ExprF(Expr1,...,ExprN), ExprF must be │ │ │ +This is often referred to as a remote or external function call.

    Example:

    lists:keyfind(Name, 1, List)

    In the second form of function calls, ExprF(Expr1,...,ExprN), ExprF must be │ │ │ an atom or evaluate to a fun.

    If ExprF is an atom, the function is said to be called by using the │ │ │ implicitly qualified function name. If the function ExprF is locally │ │ │ defined, it is called. Alternatively, if ExprF is explicitly imported from the │ │ │ M module, M:ExprF(Expr1,...,ExprN) is called. If ExprF is neither declared │ │ │ locally nor explicitly imported, ExprF must be the name of an automatically │ │ │ -imported BIF.

    Examples:

    handle(Msg, State)
    │ │ │ -spawn(m, init, [])

    Examples where ExprF is a fun:

    1> Fun1 = fun(X) -> X+1 end,
    │ │ │ -Fun1(3).
    │ │ │ +imported BIF.

    Examples:

    handle(Msg, State)
    │ │ │ +spawn(m, init, [])

    Examples where ExprF is a fun:

    1> Fun1 = fun(X) -> X+1 end,
    │ │ │ +Fun1(3).
    │ │ │  4
    │ │ │ -2> fun lists:append/2([1,2], [3,4]).
    │ │ │ -[1,2,3,4]
    │ │ │ +2> fun lists:append/2([1,2], [3,4]).
    │ │ │ +[1,2,3,4]
    │ │ │  3>

    Notice that when calling a local function, there is a difference between using │ │ │ the implicitly or fully qualified function name. The latter always refers to the │ │ │ latest version of the module. See │ │ │ Compilation and Code Loading and │ │ │ Function Evaluation.

    │ │ │ │ │ │ │ │ │ @@ -316,40 +316,40 @@ │ │ │ called instead. This is to avoid that future additions to the set of │ │ │ auto-imported BIFs do not silently change the behavior of old code.

    However, to avoid that old (pre R14) code changed its behavior when compiled │ │ │ with Erlang/OTP version R14A or later, the following restriction applies: If you │ │ │ override the name of a BIF that was auto-imported in OTP versions prior to R14A │ │ │ (ERTS version 5.8) and have an implicitly qualified call to that function in │ │ │ your code, you either need to explicitly remove the auto-import using a compiler │ │ │ directive, or replace the call with a fully qualified function call. Otherwise │ │ │ -you get a compilation error. See the following example:

    -export([length/1,f/1]).
    │ │ │ +you get a compilation error. See the following example:

    -export([length/1,f/1]).
    │ │ │  
    │ │ │ --compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
    │ │ │ +-compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
    │ │ │  
    │ │ │ -length([]) ->
    │ │ │ +length([]) ->
    │ │ │      0;
    │ │ │ -length([H|T]) ->
    │ │ │ -    1 + length(T). %% Calls the local function length/1
    │ │ │ +length([H|T]) ->
    │ │ │ +    1 + length(T). %% Calls the local function length/1
    │ │ │  
    │ │ │ -f(X) when erlang:length(X) > 3 -> %% Calls erlang:length/1,
    │ │ │ +f(X) when erlang:length(X) > 3 -> %% Calls erlang:length/1,
    │ │ │                                    %% which is allowed in guards
    │ │ │      long.

    The same logic applies to explicitly imported functions from other modules, as │ │ │ to locally defined functions. It is not allowed to both import a function from │ │ │ -another module and have the function declared in the module at the same time:

    -export([f/1]).
    │ │ │ +another module and have the function declared in the module at the same time:

    -export([f/1]).
    │ │ │  
    │ │ │ --compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
    │ │ │ +-compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
    │ │ │  
    │ │ │ --import(mod,[length/1]).
    │ │ │ +-import(mod,[length/1]).
    │ │ │  
    │ │ │ -f(X) when erlang:length(X) > 33 -> %% Calls erlang:length/1,
    │ │ │ +f(X) when erlang:length(X) > 33 -> %% Calls erlang:length/1,
    │ │ │                                     %% which is allowed in guards
    │ │ │  
    │ │ │ -    erlang:length(X);              %% Explicit call to erlang:length in body
    │ │ │ +    erlang:length(X);              %% Explicit call to erlang:length in body
    │ │ │  
    │ │ │ -f(X) ->
    │ │ │ -    length(X).                     %% mod:length/1 is called

    For auto-imported BIFs added in Erlang/OTP R14A and thereafter, overriding the │ │ │ +f(X) -> │ │ │ + length(X). %% mod:length/1 is called

    For auto-imported BIFs added in Erlang/OTP R14A and thereafter, overriding the │ │ │ name with a local function or explicit import is always allowed. However, if the │ │ │ -compile({no_auto_import,[F/A]) directive is not used, the compiler issues a │ │ │ warning whenever the function is called in the module using the implicitly │ │ │ qualified function name.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -361,40 +361,40 @@ │ │ │ ...; │ │ │ GuardSeqN -> │ │ │ BodyN │ │ │ end

    The branches of an if-expression are scanned sequentially until a guard │ │ │ sequence GuardSeq that evaluates to true is found. Then the corresponding │ │ │ Body (a sequence of expressions separated by ,) is evaluated.

    The return value of Body is the return value of the if expression.

    If no guard sequence is evaluated as true, an if_clause run-time error occurs. │ │ │ If necessary, the guard expression true can be used in the last branch, as │ │ │ -that guard sequence is always true.

    Example:

    is_greater_than(X, Y) ->
    │ │ │ +that guard sequence is always true.

    Example:

    is_greater_than(X, Y) ->
    │ │ │      if
    │ │ │          X > Y ->
    │ │ │              true;
    │ │ │          true -> % works as an 'else' branch
    │ │ │              false
    │ │ │      end

    │ │ │ │ │ │ │ │ │ │ │ │ Case │ │ │

    │ │ │
    case Expr of
    │ │ │ -    Pattern1 [when GuardSeq1] ->
    │ │ │ +    Pattern1 [when GuardSeq1] ->
    │ │ │          Body1;
    │ │ │      ...;
    │ │ │ -    PatternN [when GuardSeqN] ->
    │ │ │ +    PatternN [when GuardSeqN] ->
    │ │ │          BodyN
    │ │ │  end

    The expression Expr is evaluated and the patterns Pattern are sequentially │ │ │ matched against the result. If a match succeeds and the optional guard sequence │ │ │ GuardSeq is true, the corresponding Body is evaluated.

    The return value of Body is the return value of the case expression.

    If there is no matching pattern with a true guard sequence, a case_clause │ │ │ -run-time error occurs.

    Example:

    is_valid_signal(Signal) ->
    │ │ │ +run-time error occurs.

    Example:

    is_valid_signal(Signal) ->
    │ │ │      case Signal of
    │ │ │ -        {signal, _What, _From, _To} ->
    │ │ │ +        {signal, _What, _From, _To} ->
    │ │ │              true;
    │ │ │ -        {signal, _What, _To} ->
    │ │ │ +        {signal, _What, _To} ->
    │ │ │              true;
    │ │ │          _Else ->
    │ │ │              false
    │ │ │      end.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -412,57 +412,57 @@ │ │ │ the top-level of a maybe block. It matches the pattern Expr1 against │ │ │ Expr2. If the matching succeeds, any unbound variable in the pattern becomes │ │ │ bound. If the expression is the last expression in the maybe block, it also │ │ │ returns the value of Expr2. If the matching is unsuccessful, the rest of the │ │ │ expressions in the maybe block are skipped and the return value of the maybe │ │ │ block is Expr2.

    None of the variables bound in a maybe block must be used in the code that │ │ │ follows the block.

    Here is an example:

    maybe
    │ │ │ -    {ok, A} ?= a(),
    │ │ │ +    {ok, A} ?= a(),
    │ │ │      true = A >= 0,
    │ │ │ -    {ok, B} ?= b(),
    │ │ │ +    {ok, B} ?= b(),
    │ │ │      A + B
    │ │ │  end

    Let us first assume that a() returns {ok,42} and b() returns {ok,58}. │ │ │ With those return values, all of the match operators will succeed, and the │ │ │ return value of the maybe block is A + B, which is equal to 42 + 58 = 100.

    Now let us assume that a() returns error. The conditional match operator in │ │ │ {ok, A} ?= a() fails to match, and the return value of the maybe block is │ │ │ the value of the expression that failed to match, namely error. Similarly, if │ │ │ b() returns wrong, the return value of the maybe block is wrong.

    Finally, let us assume that a() returns {ok,-1}. Because true = A >= 0 uses │ │ │ the match operator =, a {badmatch,false} run-time error occurs when the │ │ │ -expression fails to match the pattern.

    The example can be written in a less succinct way using nested case expressions:

    case a() of
    │ │ │ -    {ok, A} ->
    │ │ │ +expression fails to match the pattern.

    The example can be written in a less succinct way using nested case expressions:

    case a() of
    │ │ │ +    {ok, A} ->
    │ │ │          true = A >= 0,
    │ │ │ -        case b() of
    │ │ │ -            {ok, B} ->
    │ │ │ +        case b() of
    │ │ │ +            {ok, B} ->
    │ │ │                  A + B;
    │ │ │              Other1 ->
    │ │ │                  Other1
    │ │ │          end;
    │ │ │      Other2 ->
    │ │ │          Other2
    │ │ │  end

    The maybe block can be augmented with else clauses:

    maybe
    │ │ │      Expr1,
    │ │ │      ...,
    │ │ │      ExprN
    │ │ │  else
    │ │ │ -    Pattern1 [when GuardSeq1] ->
    │ │ │ +    Pattern1 [when GuardSeq1] ->
    │ │ │          Body1;
    │ │ │      ...;
    │ │ │ -    PatternN [when GuardSeqN] ->
    │ │ │ +    PatternN [when GuardSeqN] ->
    │ │ │          BodyN
    │ │ │  end

    If a conditional match operator fails, the failed expression is matched against │ │ │ the patterns in all clauses between the else and end keywords. If a match │ │ │ succeeds and the optional guard sequence GuardSeq is true, the corresponding │ │ │ Body is evaluated. The value returned from the body is the return value of the │ │ │ maybe block.

    If there is no matching pattern with a true guard sequence, an else_clause │ │ │ run-time error occurs.

    None of the variables bound in a maybe block must be used in the else │ │ │ clauses. None of the variables bound in the else clauses must be used in the │ │ │ code that follows the maybe block.

    Here is the previous example augmented with else clauses:

    maybe
    │ │ │ -    {ok, A} ?= a(),
    │ │ │ +    {ok, A} ?= a(),
    │ │ │      true = A >= 0,
    │ │ │ -    {ok, B} ?= b(),
    │ │ │ +    {ok, B} ?= b(),
    │ │ │      A + B
    │ │ │  else
    │ │ │      error -> error;
    │ │ │      wrong -> error
    │ │ │  end

    The else clauses translate the failing value from the conditional match │ │ │ operators to the value error. If the failing value is not one of the │ │ │ recognized values, a else_clause run-time error occurs.

    │ │ │ @@ -481,18 +481,18 @@ │ │ │ {Name,Node} (or a pid located at another node), also never fails.

    │ │ │ │ │ │ │ │ │ │ │ │ Receive │ │ │

    │ │ │
    receive
    │ │ │ -    Pattern1 [when GuardSeq1] ->
    │ │ │ +    Pattern1 [when GuardSeq1] ->
    │ │ │          Body1;
    │ │ │      ...;
    │ │ │ -    PatternN [when GuardSeqN] ->
    │ │ │ +    PatternN [when GuardSeqN] ->
    │ │ │          BodyN
    │ │ │  end

    The receive expression searches for a message in the message queue that match │ │ │ one of the patterns in the clauses of the receive expression. The patterns in │ │ │ the clauses is matched against a message from top to bottom. The first message, │ │ │ from the start of the message queue, that matches will be selected. Messages are │ │ │ normally │ │ │ enqueued in the message queue in │ │ │ @@ -509,27 +509,27 @@ │ │ │ specific messages and the message queue is huge, executing such a receive │ │ │ expression might become very expensive.

    One type of receive expressions matching on only specific patterns can, │ │ │ however, be optimized by the compiler and runtime system. This in the scenario │ │ │ where you create a reference and │ │ │ match on it in all clauses of a receive expression close to where the │ │ │ reference was created. In this case only the amount of messages received after │ │ │ the reference was created needs to be inspected. For more information see the │ │ │ -Fetching Received Messages section of the Efficiency Guide.

    Example:

    wait_for_onhook() ->
    │ │ │ +Fetching Received Messages section of the Efficiency Guide.

    Example:

    wait_for_onhook() ->
    │ │ │      receive
    │ │ │          onhook ->
    │ │ │ -            disconnect(),
    │ │ │ -            idle();
    │ │ │ -        {connect, B} ->
    │ │ │ -            B ! {busy, self()},
    │ │ │ -            wait_for_onhook()
    │ │ │ +            disconnect(),
    │ │ │ +            idle();
    │ │ │ +        {connect, B} ->
    │ │ │ +            B ! {busy, self()},
    │ │ │ +            wait_for_onhook()
    │ │ │      end.

    The receive expression can be augmented with a timeout:

    receive
    │ │ │ -    Pattern1 [when GuardSeq1] ->
    │ │ │ +    Pattern1 [when GuardSeq1] ->
    │ │ │          Body1;
    │ │ │      ...;
    │ │ │ -    PatternN [when GuardSeqN] ->
    │ │ │ +    PatternN [when GuardSeqN] ->
    │ │ │          BodyN
    │ │ │  after
    │ │ │      ExprT ->
    │ │ │          BodyT
    │ │ │  end

    receive...after works exactly as receive, except that if no matching message │ │ │ has arrived within ExprT milliseconds, then BodyT is evaluated instead. The │ │ │ return value of BodyT then becomes the return value of the receive...after │ │ │ @@ -540,35 +540,35 @@ │ │ │ another short timeout) might be cheap since the timeout is short. This is │ │ │ not necessarily the case. If the patterns in the clauses of the receive │ │ │ expression only match specific messages and no such messages exist in the │ │ │ message queue, the whole message queue needs to be inspected before the │ │ │ timeout can occur. That is, the same apply as in │ │ │ the warning above.

    The atom infinity will make the process wait indefinitely for a matching │ │ │ message. This is the same as not using a timeout. It can be useful for timeout │ │ │ -values that are calculated at runtime.

    Example:

    wait_for_onhook() ->
    │ │ │ +values that are calculated at runtime.

    Example:

    wait_for_onhook() ->
    │ │ │      receive
    │ │ │          onhook ->
    │ │ │ -            disconnect(),
    │ │ │ -            idle();
    │ │ │ -        {connect, B} ->
    │ │ │ -            B ! {busy, self()},
    │ │ │ -            wait_for_onhook()
    │ │ │ +            disconnect(),
    │ │ │ +            idle();
    │ │ │ +        {connect, B} ->
    │ │ │ +            B ! {busy, self()},
    │ │ │ +            wait_for_onhook()
    │ │ │      after
    │ │ │          60000 ->
    │ │ │ -            disconnect(),
    │ │ │ -            error()
    │ │ │ +            disconnect(),
    │ │ │ +            error()
    │ │ │      end.

    It is legal to use a receive...after expression with no branches:

    receive
    │ │ │  after
    │ │ │      ExprT ->
    │ │ │          BodyT
    │ │ │  end

    This construction does not consume any messages, only suspends execution in the │ │ │ -process for ExprT milliseconds. This can be used to implement simple timers.

    Example:

    timer() ->
    │ │ │ -    spawn(m, timer, [self()]).
    │ │ │ +process for ExprT milliseconds. This can be used to implement simple timers.

    Example:

    timer() ->
    │ │ │ +    spawn(m, timer, [self()]).
    │ │ │  
    │ │ │ -timer(Pid) ->
    │ │ │ +timer(Pid) ->
    │ │ │      receive
    │ │ │      after
    │ │ │          5000 ->
    │ │ │              Pid ! timeout
    │ │ │      end.

    For more information on timers in Erlang in general, see the │ │ │ Timers section of the │ │ │ Time and Time Correction in Erlang │ │ │ @@ -610,21 +610,21 @@ │ │ │ false │ │ │ 4> 0.0 =:= -0.0. │ │ │ false │ │ │ 5> 0.0 =:= +0.0. │ │ │ true │ │ │ 6> 1 > a. │ │ │ false │ │ │ -7> #{c => 3} > #{a => 1, b => 2}. │ │ │ +7> #{c => 3} > #{a => 1, b => 2}. │ │ │ false │ │ │ -8> #{a => 1, b => 2} == #{a => 1.0, b => 2.0}. │ │ │ +8> #{a => 1, b => 2} == #{a => 1.0, b => 2.0}. │ │ │ true │ │ │ -9> <<2:2>> < <<128>>. │ │ │ +9> <<2:2>> < <<128>>. │ │ │ true │ │ │ -10> <<3:2>> < <<128>>. │ │ │ +10> <<3:2>> < <<128>>. │ │ │ false

    Note

    Prior to OTP 27, the term equivalence operators considered 0.0 │ │ │ and -0.0 to be the same term.

    This was changed in OTP 27 but legacy code may have expected them to be │ │ │ considered the same. To help users catch errors that may arise from an │ │ │ upgrade, the compiler raises a warning when 0.0 is pattern-matched or used │ │ │ in a term equivalence test.

    If you need to match 0.0 specifically, the warning can be silenced by │ │ │ writing +0.0 instead, which produces the same term but makes the compiler │ │ │ interpret the match as being done on purpose.

    │ │ │ @@ -650,15 +650,15 @@ │ │ │ 0 │ │ │ 8> 2#10 bor 2#01. │ │ │ 3 │ │ │ 9> a + 10. │ │ │ ** exception error: an error occurred when evaluating an arithmetic expression │ │ │ in operator +/2 │ │ │ called as a + 10 │ │ │ -10> 1 bsl (1 bsl 64). │ │ │ +10> 1 bsl (1 bsl 64). │ │ │ ** exception error: a system limit has been reached │ │ │ in operator bsl/2 │ │ │ called as 1 bsl 18446744073709551616

    │ │ │ │ │ │ │ │ │ │ │ │ Boolean Expressions │ │ │ @@ -677,136 +677,136 @@ │ │ │ │ │ │ │ │ │ │ │ │ Short-Circuit Expressions │ │ │

    │ │ │
    Expr1 orelse Expr2
    │ │ │  Expr1 andalso Expr2

    Expr2 is evaluated only if necessary. That is, Expr2 is evaluated only if:

    • Expr1 evaluates to false in an orelse expression.

    or

    • Expr1 evaluates to true in an andalso expression.

    Returns either the value of Expr1 (that is, true or false) or the value of │ │ │ -Expr2 (if Expr2 is evaluated).

    Example 1:

    case A >= -1.0 andalso math:sqrt(A+1) > B of

    This works even if A is less than -1.0, since in that case, math:sqrt/1 is │ │ │ -never evaluated.

    Example 2:

    OnlyOne = is_atom(L) orelse
    │ │ │ -         (is_list(L) andalso length(L) == 1),

    Expr2 is not required to evaluate to a Boolean value. Because of that, │ │ │ -andalso and orelse are tail-recursive.

    Example 3 (tail-recursive function):

    all(Pred, [Hd|Tail]) ->
    │ │ │ -    Pred(Hd) andalso all(Pred, Tail);
    │ │ │ -all(_, []) ->
    │ │ │ +Expr2 (if Expr2 is evaluated).

    Example 1:

    case A >= -1.0 andalso math:sqrt(A+1) > B of

    This works even if A is less than -1.0, since in that case, math:sqrt/1 is │ │ │ +never evaluated.

    Example 2:

    OnlyOne = is_atom(L) orelse
    │ │ │ +         (is_list(L) andalso length(L) == 1),

    Expr2 is not required to evaluate to a Boolean value. Because of that, │ │ │ +andalso and orelse are tail-recursive.

    Example 3 (tail-recursive function):

    all(Pred, [Hd|Tail]) ->
    │ │ │ +    Pred(Hd) andalso all(Pred, Tail);
    │ │ │ +all(_, []) ->
    │ │ │      true.

    Change

    Before Erlang/OTP R13A, Expr2 was required to evaluate to a Boolean value, │ │ │ and as consequence, andalso and orelse were not tail-recursive.

    │ │ │ │ │ │ │ │ │ │ │ │ List Operations │ │ │

    │ │ │
    Expr1 ++ Expr2
    │ │ │  Expr1 -- Expr2

    The list concatenation operator ++ appends its second argument to its first │ │ │ and returns the resulting list.

    The list subtraction operator -- produces a list that is a copy of the first │ │ │ argument. The procedure is as follows: for each element in the second argument, │ │ │ -the first occurrence of this element (if any) is removed.

    Example:

    1> [1,2,3] ++ [4,5].
    │ │ │ -[1,2,3,4,5]
    │ │ │ -2> [1,2,3,2,1,2] -- [2,1,2].
    │ │ │ -[3,1,2]

    │ │ │ +the first occurrence of this element (if any) is removed.

    Example:

    1> [1,2,3] ++ [4,5].
    │ │ │ +[1,2,3,4,5]
    │ │ │ +2> [1,2,3,2,1,2] -- [2,1,2].
    │ │ │ +[3,1,2]

    │ │ │ │ │ │ │ │ │ │ │ │ Map Expressions │ │ │

    │ │ │

    │ │ │ │ │ │ │ │ │ │ │ │ Creating Maps │ │ │

    │ │ │

    Constructing a new map is done by letting an expression K be associated with │ │ │ -another expression V:

    #{K => V}

    New maps can include multiple associations at construction by listing every │ │ │ -association:

    #{K1 => V1, ..., Kn => Vn}

    An empty map is constructed by not associating any terms with each other:

    #{}

    All keys and values in the map are terms. Any expression is first evaluated and │ │ │ +another expression V:

    #{K => V}

    New maps can include multiple associations at construction by listing every │ │ │ +association:

    #{K1 => V1, ..., Kn => Vn}

    An empty map is constructed by not associating any terms with each other:

    #{}

    All keys and values in the map are terms. Any expression is first evaluated and │ │ │ then the resulting terms are used as key and value respectively.

    Keys and values are separated by the => arrow and associations are separated │ │ │ -by a comma (,).

    Examples:

    M0 = #{},                 % empty map
    │ │ │ -M1 = #{a => <<"hello">>}, % single association with literals
    │ │ │ -M2 = #{1 => 2, b => b},   % multiple associations with literals
    │ │ │ -M3 = #{k => {A,B}},       % single association with variables
    │ │ │ -M4 = #{{"w", 1} => f()}.  % compound key associated with an evaluated expression

    Here, A and B are any expressions and M0 through M4 are the resulting │ │ │ -map terms.

    If two matching keys are declared, the latter key takes precedence.

    Example:

    1> #{1 => a, 1 => b}.
    │ │ │ -#{1 => b }
    │ │ │ -2> #{1.0 => a, 1 => b}.
    │ │ │ -#{1 => b, 1.0 => a}

    The order in which the expressions constructing the keys (and their associated │ │ │ +by a comma (,).

    Examples:

    M0 = #{},                 % empty map
    │ │ │ +M1 = #{a => <<"hello">>}, % single association with literals
    │ │ │ +M2 = #{1 => 2, b => b},   % multiple associations with literals
    │ │ │ +M3 = #{k => {A,B}},       % single association with variables
    │ │ │ +M4 = #{{"w", 1} => f()}.  % compound key associated with an evaluated expression

    Here, A and B are any expressions and M0 through M4 are the resulting │ │ │ +map terms.

    If two matching keys are declared, the latter key takes precedence.

    Example:

    1> #{1 => a, 1 => b}.
    │ │ │ +#{1 => b }
    │ │ │ +2> #{1.0 => a, 1 => b}.
    │ │ │ +#{1 => b, 1.0 => a}

    The order in which the expressions constructing the keys (and their associated │ │ │ values) are evaluated is not defined. The syntactic order of the key-value pairs │ │ │ in the construction is of no relevance, except in the recently mentioned case of │ │ │ two matching keys.

    │ │ │ │ │ │ │ │ │ │ │ │ Updating Maps │ │ │

    │ │ │

    Updating a map has a similar syntax as constructing it.

    An expression defining the map to be updated is put in front of the expression │ │ │ -defining the keys to be updated and their respective values:

    M#{K => V}

    Here M is a term of type map and K and V are any expression.

    If key K does not match any existing key in the map, a new association is │ │ │ +defining the keys to be updated and their respective values:

    M#{K => V}

    Here M is a term of type map and K and V are any expression.

    If key K does not match any existing key in the map, a new association is │ │ │ created from key K to value V.

    If key K matches an existing key in map M, its associated value is replaced │ │ │ by the new value V. In both cases, the evaluated map expression returns a new │ │ │ -map.

    If M is not of type map, an exception of type badmap is raised.

    To only update an existing value, the following syntax is used:

    M#{K := V}

    Here M is a term of type map, V is an expression and K is an expression │ │ │ +map.

    If M is not of type map, an exception of type badmap is raised.

    To only update an existing value, the following syntax is used:

    M#{K := V}

    Here M is a term of type map, V is an expression and K is an expression │ │ │ that evaluates to an existing key in M.

    If key K does not match any existing keys in map M, an exception of type │ │ │ badkey is raised at runtime. If a matching key K is present in map M, │ │ │ its associated value is replaced by the new value V, and the evaluated map │ │ │ -expression returns a new map.

    If M is not of type map, an exception of type badmap is raised.

    Examples:

    M0 = #{},
    │ │ │ -M1 = M0#{a => 0},
    │ │ │ -M2 = M1#{a => 1, b => 2},
    │ │ │ -M3 = M2#{"function" => fun() -> f() end},
    │ │ │ -M4 = M3#{a := 2, b := 3}.  % 'a' and 'b' was added in `M1` and `M2`.

    Here M0 is any map. It follows that M1 through M4 are maps as well.

    More examples:

    1> M = #{1 => a}.
    │ │ │ -#{1 => a }
    │ │ │ -2> M#{1.0 => b}.
    │ │ │ -#{1 => a, 1.0 => b}.
    │ │ │ -3> M#{1 := b}.
    │ │ │ -#{1 => b}
    │ │ │ -4> M#{1.0 := b}.
    │ │ │ +expression returns a new map.

    If M is not of type map, an exception of type badmap is raised.

    Examples:

    M0 = #{},
    │ │ │ +M1 = M0#{a => 0},
    │ │ │ +M2 = M1#{a => 1, b => 2},
    │ │ │ +M3 = M2#{"function" => fun() -> f() end},
    │ │ │ +M4 = M3#{a := 2, b := 3}.  % 'a' and 'b' was added in `M1` and `M2`.

    Here M0 is any map. It follows that M1 through M4 are maps as well.

    More examples:

    1> M = #{1 => a}.
    │ │ │ +#{1 => a }
    │ │ │ +2> M#{1.0 => b}.
    │ │ │ +#{1 => a, 1.0 => b}.
    │ │ │ +3> M#{1 := b}.
    │ │ │ +#{1 => b}
    │ │ │ +4> M#{1.0 := b}.
    │ │ │  ** exception error: bad argument

    As in construction, the order in which the key and value expressions are │ │ │ evaluated is not defined. The syntactic order of the key-value pairs in the │ │ │ update is of no relevance, except in the case where two keys match. In that │ │ │ case, the latter value is used.

    │ │ │ │ │ │ │ │ │ │ │ │ Maps in Patterns │ │ │

    │ │ │ -

    Matching of key-value associations from maps is done as follows:

    #{K := V} = M

    Here M is any map. The key K must be a │ │ │ +

    Matching of key-value associations from maps is done as follows:

    #{K := V} = M

    Here M is any map. The key K must be a │ │ │ guard expression, with all variables already │ │ │ bound. V can be any pattern with either bound or unbound variables.

    If the variable V is unbound, it becomes bound to the value associated with │ │ │ the key K, which must exist in the map M. If the variable V is bound, it │ │ │ must match the value associated with K in M.

    Change

    Before Erlang/OTP 23, the expression defining the key K was restricted to be │ │ │ -either a single variable or a literal.

    Example:

    1> M = #{"tuple" => {1,2}}.
    │ │ │ -#{"tuple" => {1,2}}
    │ │ │ -2> #{"tuple" := {1,B}} = M.
    │ │ │ -#{"tuple" => {1,2}}
    │ │ │ +either a single variable or a literal.

    Example:

    1> M = #{"tuple" => {1,2}}.
    │ │ │ +#{"tuple" => {1,2}}
    │ │ │ +2> #{"tuple" := {1,B}} = M.
    │ │ │ +#{"tuple" => {1,2}}
    │ │ │  3> B.
    │ │ │ -2.

    This binds variable B to integer 2.

    Similarly, multiple values from the map can be matched:

    #{K1 := V1, ..., Kn := Vn} = M

    Here keys K1 through Kn are any expressions with literals or bound │ │ │ +2.

    This binds variable B to integer 2.

    Similarly, multiple values from the map can be matched:

    #{K1 := V1, ..., Kn := Vn} = M

    Here keys K1 through Kn are any expressions with literals or bound │ │ │ variables. If all key expressions evaluate successfully and all keys │ │ │ exist in map M, all variables in V1 .. Vn is matched to the │ │ │ associated values of their respective keys.

    If the matching conditions are not met the match fails.

    Note that when matching a map, only the := operator (not the =>) is allowed │ │ │ as a delimiter for the associations.

    The order in which keys are declared in matching has no relevance.

    Duplicate keys are allowed in matching and match each pattern associated to the │ │ │ -keys:

    #{K := V1, K := V2} = M

    The empty map literal (#{}) matches any map when used as a pattern:

    #{} = Expr

    This expression matches if the expression Expr is of type map, otherwise it │ │ │ -fails with an exception badmatch.

    Here the key to be retrieved is constructed from an expression:

    #{{tag,length(List)} := V} = Map

    List must be an already bound variable.

    Matching Syntax

    Matching of literals as keys are allowed in function heads:

    %% only start if not_started
    │ │ │ -handle_call(start, From, #{state := not_started} = S) ->
    │ │ │ +keys:

    #{K := V1, K := V2} = M

    The empty map literal (#{}) matches any map when used as a pattern:

    #{} = Expr

    This expression matches if the expression Expr is of type map, otherwise it │ │ │ +fails with an exception badmatch.

    Here the key to be retrieved is constructed from an expression:

    #{{tag,length(List)} := V} = Map

    List must be an already bound variable.

    Matching Syntax

    Matching of literals as keys are allowed in function heads:

    %% only start if not_started
    │ │ │ +handle_call(start, From, #{state := not_started} = S) ->
    │ │ │  ...
    │ │ │ -    {reply, ok, S#{state := start}};
    │ │ │ +    {reply, ok, S#{state := start}};
    │ │ │  
    │ │ │  %% only change if started
    │ │ │ -handle_call(change, From, #{state := start} = S) ->
    │ │ │ +handle_call(change, From, #{state := start} = S) ->
    │ │ │  ...
    │ │ │ -    {reply, ok, S#{state := changed}};

    │ │ │ + {reply, ok, S#{state := changed}};

    │ │ │ │ │ │ │ │ │ │ │ │ Maps in Guards │ │ │

    │ │ │

    Maps are allowed in guards as long as all subexpressions are valid guard │ │ │ expressions.

    The following guard BIFs handle maps:

    │ │ │ │ │ │ │ │ │ │ │ │ Bit Syntax Expressions │ │ │

    │ │ │

    The bit syntax operates on bit strings. A bit string is a sequence of bits │ │ │ -ordered from the most significant bit to the least significant bit.

    <<>>  % The empty bit string, zero length
    │ │ │ -<<E1>>
    │ │ │ -<<E1,...,En>>

    Each element Ei specifies a segment of the bit string. The segments are │ │ │ +ordered from the most significant bit to the least significant bit.

    <<>>  % The empty bit string, zero length
    │ │ │ +<<E1>>
    │ │ │ +<<E1,...,En>>

    Each element Ei specifies a segment of the bit string. The segments are │ │ │ ordered left to right from the most significant bit to the least significant bit │ │ │ of the bit string.

    Each segment specification Ei is a value, whose default type is integer, │ │ │ followed by an optional size expression and an optional type specifier list.

    Ei = Value |
    │ │ │       Value:Size |
    │ │ │       Value/TypeSpecifierList |
    │ │ │       Value:Size/TypeSpecifierList

    When used in a bit string construction, Value is an expression that is to │ │ │ evaluate to an integer, float, or bit string. If the expression is not a single │ │ │ @@ -817,34 +817,34 @@ │ │ │ guard expression that evaluates to an │ │ │ integer. All variables in the guard expression must be already bound.

    Change

    Before Erlang/OTP 23, Size was restricted to be an integer or a variable │ │ │ bound to an integer.

    The value of Size specifies the size of the segment in units (see below). The │ │ │ default value depends on the type (see below):

    • For integer it is 8.
    • For float it is 64.
    • For binary and bitstring it is the whole binary or bit string.

    In matching, the default value for a binary or bit string segment is only valid │ │ │ for the last element. All other bit string or binary elements in the matching │ │ │ must have a size specification.

    Binaries

    A bit string with a length that is a multiple of 8 bits is known as a binary, │ │ │ which is the most common and useful type of bit string.

    A binary has a canonical representation in memory. Here follows a sequence of │ │ │ -bytes where each byte's value is its sequence number:

    <<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>>

    Bit strings are a later generalization of binaries, so many texts and much │ │ │ -information about binaries apply just as well for bit strings.

    Example:

    1> <<A/binary, B/binary>> = <<"abcde">>.
    │ │ │ +bytes where each byte's value is its sequence number:

    <<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>>

    Bit strings are a later generalization of binaries, so many texts and much │ │ │ +information about binaries apply just as well for bit strings.

    Example:

    1> <<A/binary, B/binary>> = <<"abcde">>.
    │ │ │  * 1:3: a binary field without size is only allowed at the end of a binary pattern
    │ │ │ -2> <<A:3/binary, B/binary>> = <<"abcde">>.
    │ │ │ -<<"abcde">>
    │ │ │ +2> <<A:3/binary, B/binary>> = <<"abcde">>.
    │ │ │ +<<"abcde">>
    │ │ │  3> A.
    │ │ │ -<<"abc">>
    │ │ │ +<<"abc">>
    │ │ │  4> B.
    │ │ │ -<<"de">>

    For the utf8, utf16, and utf32 types, Size must not be given. The size │ │ │ +<<"de">>

    For the utf8, utf16, and utf32 types, Size must not be given. The size │ │ │ of the segment is implicitly determined by the type and value itself.

    TypeSpecifierList is a list of type specifiers, in any order, separated by │ │ │ hyphens (-). Default values are used for any omitted type specifiers.

    • Type= integer | float | binary | bytes | bitstring | bits | │ │ │ utf8 | utf16 | utf32 - The default is integer. bytes is a │ │ │ shorthand for binary and bits is a shorthand for bitstring. See below │ │ │ for more information about the utf types.

    • Signedness= signed | unsigned - Only matters for matching and when │ │ │ the type is integer. The default is unsigned.

    • Endianness= big | little | native - Specifies byte level (octet │ │ │ level) endianness (byte order). Native-endian means that the endianness is │ │ │ resolved at load time to be either big-endian or little-endian, depending on │ │ │ what is native for the CPU that the Erlang machine is run on. Endianness only │ │ │ matters when the Type is either integer, utf16, utf32, or float. The │ │ │ -default is big.

      <<16#1234:16/little>> = <<16#3412:16>> = <<16#34:8, 16#12:8>>
    • Unit= unit:IntegerLiteral - The allowed range is 1 through 256. │ │ │ +default is big.

      <<16#1234:16/little>> = <<16#3412:16>> = <<16#34:8, 16#12:8>>
    • Unit= unit:IntegerLiteral - The allowed range is 1 through 256. │ │ │ Defaults to 1 for integer, float, and bitstring, and to 8 for binary. │ │ │ For types bitstring, bits, and bytes, it is not allowed to specify a │ │ │ unit value different from the default value. No unit specifier must be given │ │ │ for the types utf8, utf16, and utf32.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -869,41 +869,41 @@ │ │ │ │ │ │ Binary segments │ │ │

    │ │ │

    In this section, the phrase "binary segment" refers to any one of the segment │ │ │ types binary, bitstring, bytes, and bits.

    See also the paragraphs about Binaries.

    When constructing binaries and no size is specified for a binary segment, the │ │ │ entire binary value is interpolated into the binary being constructed. However, │ │ │ the size in bits of the binary being interpolated must be evenly divisible by │ │ │ -the unit value for the segment; otherwise an exception is raised.

    For example, the following examples all succeed:

    1> <<(<<"abc">>)/bitstring>>.
    │ │ │ -<<"abc">>
    │ │ │ -2> <<(<<"abc">>)/binary-unit:1>>.
    │ │ │ -<<"abc">>
    │ │ │ -3> <<(<<"abc">>)/binary>>.
    │ │ │ -<<"abc">>

    The first two examples have a unit value of 1 for the segment, while the third │ │ │ +the unit value for the segment; otherwise an exception is raised.

    For example, the following examples all succeed:

    1> <<(<<"abc">>)/bitstring>>.
    │ │ │ +<<"abc">>
    │ │ │ +2> <<(<<"abc">>)/binary-unit:1>>.
    │ │ │ +<<"abc">>
    │ │ │ +3> <<(<<"abc">>)/binary>>.
    │ │ │ +<<"abc">>

    The first two examples have a unit value of 1 for the segment, while the third │ │ │ segment has a unit value of 8.

    Attempting to interpolate a bit string of size 1 into a binary segment with unit │ │ │ -8 (the default unit for binary) fails as shown in this example:

    1> <<(<<1:1>>)/binary>>.
    │ │ │ -** exception error: bad argument

    For the construction to succeed, the unit value of the segment must be 1:

    2> <<(<<1:1>>)/bitstring>>.
    │ │ │ -<<1:1>>
    │ │ │ -3> <<(<<1:1>>)/binary-unit:1>>.
    │ │ │ -<<1:1>>

    Similarly, when matching a binary segment with no size specified, the match │ │ │ +8 (the default unit for binary) fails as shown in this example:

    1> <<(<<1:1>>)/binary>>.
    │ │ │ +** exception error: bad argument

    For the construction to succeed, the unit value of the segment must be 1:

    2> <<(<<1:1>>)/bitstring>>.
    │ │ │ +<<1:1>>
    │ │ │ +3> <<(<<1:1>>)/binary-unit:1>>.
    │ │ │ +<<1:1>>

    Similarly, when matching a binary segment with no size specified, the match │ │ │ succeeds if and only if the size in bits of the rest of the binary is evenly │ │ │ -divisible by the unit value:

    1> <<_/binary-unit:16>> = <<"">>.
    │ │ │ -<<>>
    │ │ │ -2> <<_/binary-unit:16>> = <<"a">>.
    │ │ │ +divisible by the unit value:

    1> <<_/binary-unit:16>> = <<"">>.
    │ │ │ +<<>>
    │ │ │ +2> <<_/binary-unit:16>> = <<"a">>.
    │ │ │  ** exception error: no match of right hand side value <<"a">>
    │ │ │ -3> <<_/binary-unit:16>> = <<"ab">>.
    │ │ │ -<<"ab">>
    │ │ │ -4> <<_/binary-unit:16>> = <<"abc">>.
    │ │ │ +3> <<_/binary-unit:16>> = <<"ab">>.
    │ │ │ +<<"ab">>
    │ │ │ +4> <<_/binary-unit:16>> = <<"abc">>.
    │ │ │  ** exception error: no match of right hand side value <<"abc">>
    │ │ │ -5> <<_/binary-unit:16>> = <<"abcd">>.
    │ │ │ -<<"abcd">>

    When a size is explicitly specified for a binary segment, the segment size in │ │ │ +5> <<_/binary-unit:16>> = <<"abcd">>. │ │ │ +<<"abcd">>

    When a size is explicitly specified for a binary segment, the segment size in │ │ │ bits is the value of Size multiplied by the default or explicit unit value.

    When constructing binaries, the size of the binary being interpolated into the │ │ │ -constructed binary must be at least as large as the size of the binary segment.

    Examples:

    1> <<(<<"abc">>):2/binary>>.
    │ │ │ -<<"ab">>
    │ │ │ -2> <<(<<"a">>):2/binary>>.
    │ │ │ +constructed binary must be at least as large as the size of the binary segment.

    Examples:

    1> <<(<<"abc">>):2/binary>>.
    │ │ │ +<<"ab">>
    │ │ │ +2> <<(<<"a">>):2/binary>>.
    │ │ │  ** exception error: construction of binary failed
    │ │ │          *** segment 1 of type 'binary': the value <<"a">> is shorter than the size of the segment

    │ │ │ │ │ │ │ │ │ │ │ │ Unicode segments │ │ │

    │ │ │ @@ -919,78 +919,78 @@ │ │ │ range 0 through 16#D7FF or 16#E000 through 16#10FFFF. The match fails if the │ │ │ returned value falls outside those ranges.

    A segment of type utf8 matches 1-4 bytes in the bit string, if the bit string │ │ │ at the match position contains a valid UTF-8 sequence. (See RFC-3629 or the │ │ │ Unicode standard.)

    A segment of type utf16 can match 2 or 4 bytes in the bit string. The match │ │ │ fails if the bit string at the match position does not contain a legal UTF-16 │ │ │ encoding of a Unicode code point. (See RFC-2781 or the Unicode standard.)

    A segment of type utf32 can match 4 bytes in the bit string in the same way as │ │ │ an integer segment matches 32 bits. The match fails if the resulting integer │ │ │ -is outside the legal ranges previously mentioned.

    Examples:

    1> Bin1 = <<1,17,42>>.
    │ │ │ -<<1,17,42>>
    │ │ │ -2> Bin2 = <<"abc">>.
    │ │ │ -<<97,98,99>>
    │ │ │ +is outside the legal ranges previously mentioned.

    Examples:

    1> Bin1 = <<1,17,42>>.
    │ │ │ +<<1,17,42>>
    │ │ │ +2> Bin2 = <<"abc">>.
    │ │ │ +<<97,98,99>>
    │ │ │  
    │ │ │ -3> Bin3 = <<1,17,42:16>>.
    │ │ │ -<<1,17,0,42>>
    │ │ │ -4> <<A,B,C:16>> = <<1,17,42:16>>.
    │ │ │ -<<1,17,0,42>>
    │ │ │ +3> Bin3 = <<1,17,42:16>>.
    │ │ │ +<<1,17,0,42>>
    │ │ │ +4> <<A,B,C:16>> = <<1,17,42:16>>.
    │ │ │ +<<1,17,0,42>>
    │ │ │  5> C.
    │ │ │  42
    │ │ │ -6> <<D:16,E,F>> = <<1,17,42:16>>.
    │ │ │ -<<1,17,0,42>>
    │ │ │ +6> <<D:16,E,F>> = <<1,17,42:16>>.
    │ │ │ +<<1,17,0,42>>
    │ │ │  7> D.
    │ │ │  273
    │ │ │  8> F.
    │ │ │  42
    │ │ │ -9> <<G,H/binary>> = <<1,17,42:16>>.
    │ │ │ -<<1,17,0,42>>
    │ │ │ +9> <<G,H/binary>> = <<1,17,42:16>>.
    │ │ │ +<<1,17,0,42>>
    │ │ │  10> H.
    │ │ │ -<<17,0,42>>
    │ │ │ -11> <<G,J/bitstring>> = <<1,17,42:12>>.
    │ │ │ -<<1,17,2,10:4>>
    │ │ │ +<<17,0,42>>
    │ │ │ +11> <<G,J/bitstring>> = <<1,17,42:12>>.
    │ │ │ +<<1,17,2,10:4>>
    │ │ │  12> J.
    │ │ │ -<<17,2,10:4>>
    │ │ │ +<<17,2,10:4>>
    │ │ │  
    │ │ │ -13> <<1024/utf8>>.
    │ │ │ -<<208,128>>
    │ │ │ +13> <<1024/utf8>>.
    │ │ │ +<<208,128>>
    │ │ │  
    │ │ │ -14> <<1:1,0:7>>.
    │ │ │ -<<128>>
    │ │ │ -15> <<16#123:12/little>> = <<16#231:12>> = <<2:4, 3:4, 1:4>>.
    │ │ │ -<<35,1:4>>

    Notice that bit string patterns cannot be nested.

    Notice also that "B=<<1>>" is interpreted as "B =< <1>>" which is a syntax │ │ │ +14> <<1:1,0:7>>. │ │ │ +<<128>> │ │ │ +15> <<16#123:12/little>> = <<16#231:12>> = <<2:4, 3:4, 1:4>>. │ │ │ +<<35,1:4>>

    Notice that bit string patterns cannot be nested.

    Notice also that "B=<<1>>" is interpreted as "B =< <1>>" which is a syntax │ │ │ error. The correct way is to write a space after =: "B = <<1>>.

    More examples are provided in Programming Examples.

    │ │ │ │ │ │ │ │ │ │ │ │ Fun Expressions │ │ │

    │ │ │
    fun
    │ │ │ -    [Name](Pattern11,...,Pattern1N) [when GuardSeq1] ->
    │ │ │ +    [Name](Pattern11,...,Pattern1N) [when GuardSeq1] ->
    │ │ │                Body1;
    │ │ │      ...;
    │ │ │ -    [Name](PatternK1,...,PatternKN) [when GuardSeqK] ->
    │ │ │ +    [Name](PatternK1,...,PatternKN) [when GuardSeqK] ->
    │ │ │                BodyK
    │ │ │  end

    A fun expression begins with the keyword fun and ends with the keyword end. │ │ │ Between them is to be a function declaration, similar to a │ │ │ regular function declaration, │ │ │ except that the function name is optional and is to be a variable, if any.

    Variables in a fun head shadow the function name and both shadow variables in │ │ │ the function clause surrounding the fun expression. Variables bound in a fun │ │ │ -body are local to the fun body.

    The return value of the expression is the resulting fun.

    Examples:

    1> Fun1 = fun (X) -> X+1 end.
    │ │ │ +body are local to the fun body.

    The return value of the expression is the resulting fun.

    Examples:

    1> Fun1 = fun (X) -> X+1 end.
    │ │ │  #Fun<erl_eval.6.39074546>
    │ │ │ -2> Fun1(2).
    │ │ │ +2> Fun1(2).
    │ │ │  3
    │ │ │ -3> Fun2 = fun (X) when X>=5 -> gt; (X) -> lt end.
    │ │ │ +3> Fun2 = fun (X) when X>=5 -> gt; (X) -> lt end.
    │ │ │  #Fun<erl_eval.6.39074546>
    │ │ │ -4> Fun2(7).
    │ │ │ +4> Fun2(7).
    │ │ │  gt
    │ │ │ -5> Fun3 = fun Fact(1) -> 1; Fact(X) when X > 1 -> X * Fact(X - 1) end.
    │ │ │ +5> Fun3 = fun Fact(1) -> 1; Fact(X) when X > 1 -> X * Fact(X - 1) end.
    │ │ │  #Fun<erl_eval.6.39074546>
    │ │ │ -6> Fun3(4).
    │ │ │ +6> Fun3(4).
    │ │ │  24

    The following fun expressions are also allowed:

    fun Name/Arity
    │ │ │  fun Module:Name/Arity

    In Name/Arity, Name is an atom and Arity is an integer. Name/Arity must │ │ │ -specify an existing local function. The expression is syntactic sugar for:

    fun (Arg1,...,ArgN) -> Name(Arg1,...,ArgN) end

    In Module:Name/Arity, Module, and Name are atoms and Arity is an │ │ │ +specify an existing local function. The expression is syntactic sugar for:

    fun (Arg1,...,ArgN) -> Name(Arg1,...,ArgN) end

    In Module:Name/Arity, Module, and Name are atoms and Arity is an │ │ │ integer. Module, Name, and Arity can also be variables. A fun defined in │ │ │ this way refers to the function Name with arity Arity in the latest │ │ │ version of module Module. A fun defined in this way is not dependent on the │ │ │ code for the module in which it is defined.

    Change

    Before Erlang/OTP R15, Module, Name, and Arity were not allowed to be │ │ │ variables.

    More examples are provided in Programming Examples.

    │ │ │ │ │ │ │ │ │ @@ -1000,35 +1000,35 @@ │ │ │
    catch Expr

    Returns the value of Expr unless an exception is raised during the evaluation. In │ │ │ that case, the exception is caught. The return value depends on the class of the │ │ │ exception:

    Reason depends on the type of error that occurred, and Stack is the stack of │ │ │ recent function calls, see Exit Reasons.

    Examples:

    1> catch 1+2.
    │ │ │  3
    │ │ │  2> catch 1+a.
    │ │ │ -{'EXIT',{badarith,[...]}}

    The BIF throw(Any) can be used for non-local return from a │ │ │ -function. It must be evaluated within a catch, which returns the value Any.

    Example:

    3> catch throw(hello).
    │ │ │ +{'EXIT',{badarith,[...]}}

    The BIF throw(Any) can be used for non-local return from a │ │ │ +function. It must be evaluated within a catch, which returns the value Any.

    Example:

    3> catch throw(hello).
    │ │ │  hello

    If throw/1 is not evaluated within a catch, a nocatch run-time │ │ │ error occurs.

    Change

    Before Erlang/OTP 24, the catch operator had the lowest precedence, making │ │ │ -it necessary to add parentheses when combining it with the match operator:

    1> A = (catch 42).
    │ │ │ +it necessary to add parentheses when combining it with the match operator:

    1> A = (catch 42).
    │ │ │  42
    │ │ │  2> A.
    │ │ │  42

    Starting from Erlang/OTP 24, the parentheses can be omitted:

    1> A = catch 42.
    │ │ │  42
    │ │ │  2> A.
    │ │ │  42

    │ │ │ │ │ │ │ │ │ │ │ │ Try │ │ │

    │ │ │
    try Exprs
    │ │ │  catch
    │ │ │ -    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
    │ │ │ +    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
    │ │ │          ExceptionBody1;
    │ │ │ -    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
    │ │ │ +    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
    │ │ │          ExceptionBodyN
    │ │ │  end

    This is an enhancement of catch. It gives the │ │ │ possibility to:

    • Distinguish between different exception classes.
    • Choose to handle only the desired ones.
    • Passing the others on to an enclosing try or catch, or to default error │ │ │ handling.

    Notice that although the keyword catch is used in the try expression, there │ │ │ is not a catch expression within the try expression.

    It returns the value of Exprs (a sequence of expressions Expr1, ..., ExprN) │ │ │ unless an exception occurs during the evaluation. In that case the exception is │ │ │ caught and the patterns ExceptionPattern with the right exception class │ │ │ @@ -1038,47 +1038,47 @@ │ │ │ stack trace is bound to the variable when the corresponding ExceptionPattern │ │ │ matches.

    If an exception occurs during evaluation of Exprs but there is no matching │ │ │ ExceptionPattern of the right Class with a true guard sequence, the │ │ │ exception is passed on as if Exprs had not been enclosed in a try │ │ │ expression.

    If an exception occurs during evaluation of ExceptionBody, it is not caught.

    It is allowed to omit Class and Stacktrace. An omitted Class is shorthand │ │ │ for throw:

    try Exprs
    │ │ │  catch
    │ │ │ -    ExceptionPattern1 [when ExceptionGuardSeq1] ->
    │ │ │ +    ExceptionPattern1 [when ExceptionGuardSeq1] ->
    │ │ │          ExceptionBody1;
    │ │ │ -    ExceptionPatternN [when ExceptionGuardSeqN] ->
    │ │ │ +    ExceptionPatternN [when ExceptionGuardSeqN] ->
    │ │ │          ExceptionBodyN
    │ │ │  end

    The try expression can have an of section:

    try Exprs of
    │ │ │ -    Pattern1 [when GuardSeq1] ->
    │ │ │ +    Pattern1 [when GuardSeq1] ->
    │ │ │          Body1;
    │ │ │      ...;
    │ │ │ -    PatternN [when GuardSeqN] ->
    │ │ │ +    PatternN [when GuardSeqN] ->
    │ │ │          BodyN
    │ │ │  catch
    │ │ │ -    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
    │ │ │ +    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
    │ │ │          ExceptionBody1;
    │ │ │      ...;
    │ │ │ -    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
    │ │ │ +    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
    │ │ │          ExceptionBodyN
    │ │ │  end

    If the evaluation of Exprs succeeds without an exception, the patterns │ │ │ Pattern are sequentially matched against the result in the same way as for a │ │ │ case expression, except that if the matching fails, a │ │ │ try_clause run-time error occurs instead of a case_clause.

    Only exceptions occurring during the evaluation of Exprs can be caught by the │ │ │ catch section. Exceptions occurring in a Body or due to a failed match are │ │ │ not caught.

    The try expression can also be augmented with an after section, intended to │ │ │ be used for cleanup with side effects:

    try Exprs of
    │ │ │ -    Pattern1 [when GuardSeq1] ->
    │ │ │ +    Pattern1 [when GuardSeq1] ->
    │ │ │          Body1;
    │ │ │      ...;
    │ │ │ -    PatternN [when GuardSeqN] ->
    │ │ │ +    PatternN [when GuardSeqN] ->
    │ │ │          BodyN
    │ │ │  catch
    │ │ │ -    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
    │ │ │ +    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
    │ │ │          ExceptionBody1;
    │ │ │      ...;
    │ │ │ -    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
    │ │ │ +    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
    │ │ │          ExceptionBodyN
    │ │ │  after
    │ │ │      AfterBody
    │ │ │  end

    AfterBody is evaluated after either Body or ExceptionBody, no matter which │ │ │ one. The evaluated value of AfterBody is lost; the return value of the try │ │ │ expression is the same with an after section as without.

    Even if an exception occurs during evaluation of Body or ExceptionBody, │ │ │ AfterBody is evaluated. In this case the exception is passed on after │ │ │ @@ -1101,40 +1101,40 @@ │ │ │ ExpressionBody │ │ │ after │ │ │ AfterBody │ │ │ end │ │ │ │ │ │ try Exprs after AfterBody end

    Next is an example of using after. This closes the file, even in the event of │ │ │ exceptions in file:read/2 or in binary_to_term/1. The │ │ │ -exceptions are the same as without the try...after...end expression:

    termize_file(Name) ->
    │ │ │ -    {ok,F} = file:open(Name, [read,binary]),
    │ │ │ +exceptions are the same as without the try...after...end expression:

    termize_file(Name) ->
    │ │ │ +    {ok,F} = file:open(Name, [read,binary]),
    │ │ │      try
    │ │ │ -        {ok,Bin} = file:read(F, 1024*1024),
    │ │ │ -        binary_to_term(Bin)
    │ │ │ +        {ok,Bin} = file:read(F, 1024*1024),
    │ │ │ +        binary_to_term(Bin)
    │ │ │      after
    │ │ │ -        file:close(F)
    │ │ │ +        file:close(F)
    │ │ │      end.

    Next is an example of using try to emulate catch Expr:

    try Expr
    │ │ │  catch
    │ │ │      throw:Term -> Term;
    │ │ │ -    exit:Reason -> {'EXIT',Reason};
    │ │ │ -    error:Reason:Stk -> {'EXIT',{Reason,Stk}}
    │ │ │ +    exit:Reason -> {'EXIT',Reason};
    │ │ │ +    error:Reason:Stk -> {'EXIT',{Reason,Stk}}
    │ │ │  end

    Variables bound in the various parts of these expressions have different scopes. │ │ │ Variables bound just after the try keyword are:

    • bound in the of section
    • unsafe in both the catch and after sections, as well as after the whole │ │ │ construct

    Variables bound in of section are:

    • unbound in the catch section
    • unsafe in both the after section, as well as after the whole construct

    Variables bound in the catch section are unsafe in the after section, as │ │ │ well as after the whole construct.

    Variables bound in the after section are unsafe after the whole construct.

    │ │ │ │ │ │ │ │ │ │ │ │ Parenthesized Expressions │ │ │

    │ │ │ -
    (Expr)

    Parenthesized expressions are useful to override │ │ │ +

    (Expr)

    Parenthesized expressions are useful to override │ │ │ operator precedences, for example, in arithmetic │ │ │ expressions:

    1> 1 + 2 * 3.
    │ │ │  7
    │ │ │ -2> (1 + 2) * 3.
    │ │ │ +2> (1 + 2) * 3.
    │ │ │  9

    │ │ │ │ │ │ │ │ │ │ │ │ Block Expressions │ │ │

    │ │ │
    begin
    │ │ │ @@ -1146,82 +1146,82 @@
    │ │ │    
    │ │ │      
    │ │ │    
    │ │ │    Comprehensions
    │ │ │  

    │ │ │

    Comprehensions provide a succinct notation for iterating over one or more terms │ │ │ and constructing a new term. Comprehensions come in three different flavors, │ │ │ -depending on the type of term they build.

    List comprehensions construct lists. They have the following syntax:

    [Expr || Qualifier1, . . ., QualifierN]

    Here, Expr is an arbitrary expression, and each Qualifier is either a │ │ │ +depending on the type of term they build.

    List comprehensions construct lists. They have the following syntax:

    [Expr || Qualifier1, . . ., QualifierN]

    Here, Expr is an arbitrary expression, and each Qualifier is either a │ │ │ generator or a filter.

    Bit string comprehensions construct bit strings or binaries. They have the │ │ │ -following syntax:

    << BitStringExpr || Qualifier1, . . ., QualifierN >>

    BitStringExpr is an expression that evaluates to a bit string. If │ │ │ +following syntax:

    << BitStringExpr || Qualifier1, . . ., QualifierN >>

    BitStringExpr is an expression that evaluates to a bit string. If │ │ │ BitStringExpr is a function call, it must be enclosed in parentheses. Each │ │ │ -Qualifier is either a generator or a filter.

    Map comprehensions construct maps. They have the following syntax:

    #{KeyExpr => ValueExpr || Qualifier1, . . ., QualifierN}

    Here, KeyExpr and ValueExpr are arbitrary expressions, and each Qualifier │ │ │ +Qualifier is either a generator or a filter.

    Map comprehensions construct maps. They have the following syntax:

    #{KeyExpr => ValueExpr || Qualifier1, . . ., QualifierN}

    Here, KeyExpr and ValueExpr are arbitrary expressions, and each Qualifier │ │ │ is either a generator or a filter.

    Change

    Map comprehensions and map generators were introduced in Erlang/OTP 26.

    There are four kinds of generators. Three of them have a relaxed and a strict │ │ │ variant. The fourth kind of generator, zip generator, is composed by two or │ │ │ more non-zip generators.

    Change

    Strict generators and zip generators were introduced in Erlang/OTP 28. │ │ │ Using strict generators is a better practice when either strict or relaxed │ │ │ generators work. More details are in │ │ │ Programming Examples.

    A list generator has the following syntax for relaxed:

    Pattern <- ListExpr

    and strict variant:

    Pattern <:- ListExpr

    where ListExpr is an expression that evaluates to a list of terms.

    A bit string generator has the following syntax for relaxed:

    BitstringPattern <= BitStringExpr

    and strict variant:

    BitstringPattern <:= BitStringExpr

    where BitStringExpr is an expression that evaluates to a bit string.

    A map generator has the following syntax for relaxed:

    KeyPattern := ValuePattern <- MapExpression

    and strict variant:

    KeyPattern := ValuePattern <:- MapExpression

    where MapExpr is an expression that evaluates to a map, or a map iterator │ │ │ obtained by calling maps:iterator/1 or maps:iterator/2.

    A zip generator has the following syntax:

    Generator_1 && ... && Generator_n

    where every Generator_i is a non-zip generator. Generators within a zip │ │ │ generator are treated as one generator and evaluated in parallel.

    A filter is an expression that evaluates to true or false.

    The variables in the generator patterns shadow previously bound variables, │ │ │ including variables bound in a previous generator pattern.

    Variables bound in a generator expression are not visible outside the │ │ │ -expression:

    1> [{E,L} || E <- L=[1,2,3]].
    │ │ │ +expression:

    1> [{E,L} || E <- L=[1,2,3]].
    │ │ │  * 1:5: variable 'L' is unbound

    A list comprehension returns a list, where the list elements are the result │ │ │ of evaluating Expr for each combination of generator elements for which all │ │ │ filters are true.

    A bit string comprehension returns a bit string, which is created by │ │ │ concatenating the results of evaluating BitStringExpr for each combination of │ │ │ bit string generator elements for which all filters are true.

    A map comprehension returns a map, where the map elements are the result of │ │ │ evaluating KeyExpr and ValueExpr for each combination of generator elements │ │ │ for which all filters are true. If the key expressions are not unique, the last │ │ │ -occurrence is stored in the map.

    Examples:

    Multiplying each element in a list by two:

    1> [X*2 || X <:- [1,2,3]].
    │ │ │ -[2,4,6]

    Multiplying each byte in a binary by two, returning a list:

    1> [X*2 || <<X>> <:= <<1,2,3>>].
    │ │ │ -[2,4,6]

    Multiplying each byte in a binary by two:

    1> << <<(X*2)>> || <<X>> <:= <<1,2,3>> >>.
    │ │ │ -<<2,4,6>>

    Multiplying each element in a list by two, returning a binary:

    1> << <<(X*2)>> || X <:- [1,2,3] >>.
    │ │ │ -<<2,4,6>>

    Creating a mapping from an integer to its square:

    1> #{X => X*X || X <:- [1,2,3]}.
    │ │ │ -#{1 => 1,2 => 4,3 => 9}

    Multiplying the value of each element in a map by two:

    1> #{K => 2*V || K := V <:- #{a => 1,b => 2,c => 3}}.
    │ │ │ -#{a => 2,b => 4,c => 6}

    Filtering a list, keeping odd numbers:

    1> [X || X <:- [1,2,3,4,5], X rem 2 =:= 1].
    │ │ │ -[1,3,5]

    Filtering a list, keeping only elements that match:

    1> [X || {_,_}=X <- [{a,b}, [a], {x,y,z}, {1,2}]].
    │ │ │ -[{a,b},{1,2}]

    Filtering a list, crashing when the element is not a 2-tuple:

    1> [X || {_,_}=X <:- [{a,b}, [a], {x,y,z}, {1,2}]].
    │ │ │ -** exception error: no match of right hand side value [a]

    Combining elements from two list generators:

    1> [{P,Q} || P <:- [a,b,c], Q <:- [1,2]].
    │ │ │ -[{a,1},{a,2},{b,1},{b,2},{c,1},{c,2}]

    Combining elements from two list generators, using a zip generator:

    1> [{P,Q} || P <:- [a,b,c] && Q <:- [1,2,3]].
    │ │ │ -[{a,1},{b,2},{c,3}]

    Combining elements from two list generators using a zip generator, filtering │ │ │ -out odd numbers:

    1> [{P,Q} || P <:- [a,b,c] && Q <:- [1,2,3], Q rem 2 =:= 0].
    │ │ │ -[{a,1},{b,2},{c,3}]

    Filtering out non-matching elements from two lists.

    1> [X || X <- [1,2,3,5] && X <- [1,4,3,6]].
    │ │ │ -[1,3]

    More examples are provided in │ │ │ +occurrence is stored in the map.

    Examples:

    Multiplying each element in a list by two:

    1> [X*2 || X <:- [1,2,3]].
    │ │ │ +[2,4,6]

    Multiplying each byte in a binary by two, returning a list:

    1> [X*2 || <<X>> <:= <<1,2,3>>].
    │ │ │ +[2,4,6]

    Multiplying each byte in a binary by two:

    1> << <<(X*2)>> || <<X>> <:= <<1,2,3>> >>.
    │ │ │ +<<2,4,6>>

    Multiplying each element in a list by two, returning a binary:

    1> << <<(X*2)>> || X <:- [1,2,3] >>.
    │ │ │ +<<2,4,6>>

    Creating a mapping from an integer to its square:

    1> #{X => X*X || X <:- [1,2,3]}.
    │ │ │ +#{1 => 1,2 => 4,3 => 9}

    Multiplying the value of each element in a map by two:

    1> #{K => 2*V || K := V <:- #{a => 1,b => 2,c => 3}}.
    │ │ │ +#{a => 2,b => 4,c => 6}

    Filtering a list, keeping odd numbers:

    1> [X || X <:- [1,2,3,4,5], X rem 2 =:= 1].
    │ │ │ +[1,3,5]

    Filtering a list, keeping only elements that match:

    1> [X || {_,_}=X <- [{a,b}, [a], {x,y,z}, {1,2}]].
    │ │ │ +[{a,b},{1,2}]

    Filtering a list, crashing when the element is not a 2-tuple:

    1> [X || {_,_}=X <:- [{a,b}, [a], {x,y,z}, {1,2}]].
    │ │ │ +** exception error: no match of right hand side value [a]

    Combining elements from two list generators:

    1> [{P,Q} || P <:- [a,b,c], Q <:- [1,2]].
    │ │ │ +[{a,1},{a,2},{b,1},{b,2},{c,1},{c,2}]

    Combining elements from two list generators, using a zip generator:

    1> [{P,Q} || P <:- [a,b,c] && Q <:- [1,2,3]].
    │ │ │ +[{a,1},{b,2},{c,3}]

    Combining elements from two list generators using a zip generator, filtering │ │ │ +out odd numbers:

    1> [{P,Q} || P <:- [a,b,c] && Q <:- [1,2,3], Q rem 2 =:= 0].
    │ │ │ +[{a,1},{b,2},{c,3}]

    Filtering out non-matching elements from two lists.

    1> [X || X <- [1,2,3,5] && X <- [1,4,3,6]].
    │ │ │ +[1,3]

    More examples are provided in │ │ │ Programming Examples.

    When there are no generators, a comprehension returns either a term constructed │ │ │ from a single element (the result of evaluating Expr) if all filters are true, │ │ │ or a term constructed from no elements (that is, [] for list comprehension, │ │ │ -<<>> for a bit string comprehension, and #{} for a map comprehension).

    Example:

    1> [2 || is_integer(2)].
    │ │ │ -[2]
    │ │ │ -2> [x || is_integer(x)].
    │ │ │ -[]

    What happens when the filter expression does not evaluate to a boolean value │ │ │ +<<>> for a bit string comprehension, and #{} for a map comprehension).

    Example:

    1> [2 || is_integer(2)].
    │ │ │ +[2]
    │ │ │ +2> [x || is_integer(x)].
    │ │ │ +[]

    What happens when the filter expression does not evaluate to a boolean value │ │ │ depends on the expression:

    • If the expression is a guard expression, │ │ │ failure to evaluate or evaluating to a non-boolean value is equivalent to │ │ │ evaluating to false.
    • If the expression is not a guard expression and evaluates to a non-Boolean │ │ │ value Val, an exception {bad_filter, Val} is triggered at runtime. If the │ │ │ evaluation of the expression raises an exception, it is not caught by the │ │ │ -comprehension.

    Examples (using a guard expression as filter):

    1> List = [1,2,a,b,c,3,4].
    │ │ │ -[1,2,a,b,c,3,4]
    │ │ │ -2> [E || E <:- List, E rem 2].
    │ │ │ -[]
    │ │ │ -3> [E || E <:- List, E rem 2 =:= 0].
    │ │ │ -[2,4]

    Examples (using a non-guard expression as filter):

    1> List = [1,2,a,b,c,3,4].
    │ │ │ -[1,2,a,b,c,3,4]
    │ │ │ -2> FaultyIsEven = fun(E) -> E rem 2 end.
    │ │ │ +comprehension.

    Examples (using a guard expression as filter):

    1> List = [1,2,a,b,c,3,4].
    │ │ │ +[1,2,a,b,c,3,4]
    │ │ │ +2> [E || E <:- List, E rem 2].
    │ │ │ +[]
    │ │ │ +3> [E || E <:- List, E rem 2 =:= 0].
    │ │ │ +[2,4]

    Examples (using a non-guard expression as filter):

    1> List = [1,2,a,b,c,3,4].
    │ │ │ +[1,2,a,b,c,3,4]
    │ │ │ +2> FaultyIsEven = fun(E) -> E rem 2 end.
    │ │ │  #Fun<erl_eval.42.17316486>
    │ │ │ -3> [E || E <:- List, FaultyIsEven(E)].
    │ │ │ +3> [E || E <:- List, FaultyIsEven(E)].
    │ │ │  ** exception error: bad filter 1
    │ │ │ -4> IsEven = fun(E) -> E rem 2 =:= 0 end.
    │ │ │ +4> IsEven = fun(E) -> E rem 2 =:= 0 end.
    │ │ │  #Fun<erl_eval.42.17316486>
    │ │ │ -5> [E || E <:- List, IsEven(E)].
    │ │ │ +5> [E || E <:- List, IsEven(E)].
    │ │ │  ** exception error: an error occurred when evaluating an arithmetic expression
    │ │ │       in operator  rem/2
    │ │ │          called as a rem 2
    │ │ │ -6> [E || E <:- List, is_integer(E), IsEven(E)].
    │ │ │ -[2,4]

    │ │ │ +6> [E || E <:- List, is_integer(E), IsEven(E)]. │ │ │ +[2,4]

    │ │ │ │ │ │ │ │ │ │ │ │ Guard Sequences │ │ │

    │ │ │

    A guard sequence is a sequence of guards, separated by semicolon (;). The │ │ │ guard sequence is true if at least one of the guards is true. (The remaining │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/funs.html │ │ │ @@ -117,402 +117,402 @@ │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ │ │ │ map │ │ │

    │ │ │ -

    The following function, double, doubles every element in a list:

    double([H|T]) -> [2*H|double(T)];
    │ │ │ -double([])    -> [].

    Hence, the argument entered as input is doubled as follows:

    > double([1,2,3,4]).
    │ │ │ -[2,4,6,8]

    The following function, add_one, adds one to every element in a list:

    add_one([H|T]) -> [H+1|add_one(T)];
    │ │ │ -add_one([])    -> [].

    The functions double and add_one have a similar structure. This can be used │ │ │ -by writing a function map that expresses this similarity:

    map(F, [H|T]) -> [F(H)|map(F, T)];
    │ │ │ -map(F, [])    -> [].

    The functions double and add_one can now be expressed in terms of map as │ │ │ -follows:

    double(L)  -> map(fun(X) -> 2*X end, L).
    │ │ │ -add_one(L) -> map(fun(X) -> 1 + X end, L).

    map(F, List) is a function that takes a function F and a list L as │ │ │ +

    The following function, double, doubles every element in a list:

    double([H|T]) -> [2*H|double(T)];
    │ │ │ +double([])    -> [].

    Hence, the argument entered as input is doubled as follows:

    > double([1,2,3,4]).
    │ │ │ +[2,4,6,8]

    The following function, add_one, adds one to every element in a list:

    add_one([H|T]) -> [H+1|add_one(T)];
    │ │ │ +add_one([])    -> [].

    The functions double and add_one have a similar structure. This can be used │ │ │ +by writing a function map that expresses this similarity:

    map(F, [H|T]) -> [F(H)|map(F, T)];
    │ │ │ +map(F, [])    -> [].

    The functions double and add_one can now be expressed in terms of map as │ │ │ +follows:

    double(L)  -> map(fun(X) -> 2*X end, L).
    │ │ │ +add_one(L) -> map(fun(X) -> 1 + X end, L).

    map(F, List) is a function that takes a function F and a list L as │ │ │ arguments and returns a new list, obtained by applying F to each of the │ │ │ elements in L.

    The process of abstracting out the common features of a number of different │ │ │ programs is called procedural abstraction. Procedural abstraction can be used │ │ │ to write several different functions that have a similar structure, but differ │ │ │ in some minor detail. This is done as follows:

    1. Step 1. Write one function that represents the common features of these │ │ │ functions.
    2. Step 2. Parameterize the difference in terms of functions that are passed │ │ │ as arguments to the common function.

    │ │ │ │ │ │ │ │ │ │ │ │ foreach │ │ │

    │ │ │

    This section illustrates procedural abstraction. Initially, the following two │ │ │ -examples are written as conventional functions.

    This function prints all elements of a list onto a stream:

    print_list(Stream, [H|T]) ->
    │ │ │ -    io:format(Stream, "~p~n", [H]),
    │ │ │ -    print_list(Stream, T);
    │ │ │ -print_list(Stream, []) ->
    │ │ │ -    true.

    This function broadcasts a message to a list of processes:

    broadcast(Msg, [Pid|Pids]) ->
    │ │ │ +examples are written as conventional functions.

    This function prints all elements of a list onto a stream:

    print_list(Stream, [H|T]) ->
    │ │ │ +    io:format(Stream, "~p~n", [H]),
    │ │ │ +    print_list(Stream, T);
    │ │ │ +print_list(Stream, []) ->
    │ │ │ +    true.

    This function broadcasts a message to a list of processes:

    broadcast(Msg, [Pid|Pids]) ->
    │ │ │      Pid ! Msg,
    │ │ │ -    broadcast(Msg, Pids);
    │ │ │ -broadcast(_, []) ->
    │ │ │ +    broadcast(Msg, Pids);
    │ │ │ +broadcast(_, []) ->
    │ │ │      true.

    These two functions have a similar structure. They both iterate over a list and │ │ │ do something to each element in the list. The "something" is passed on as an │ │ │ -extra argument to the function that does this.

    The function foreach expresses this similarity:

    foreach(F, [H|T]) ->
    │ │ │ -    F(H),
    │ │ │ -    foreach(F, T);
    │ │ │ -foreach(F, []) ->
    │ │ │ -    ok.

    Using the function foreach, the function print_list becomes:

    foreach(fun(H) -> io:format(S, "~p~n",[H]) end, L)

    Using the function foreach, the function broadcast becomes:

    foreach(fun(Pid) -> Pid ! M end, L)

    foreach is evaluated for its side-effect and not its value. foreach(Fun ,L) │ │ │ +extra argument to the function that does this.

    The function foreach expresses this similarity:

    foreach(F, [H|T]) ->
    │ │ │ +    F(H),
    │ │ │ +    foreach(F, T);
    │ │ │ +foreach(F, []) ->
    │ │ │ +    ok.

    Using the function foreach, the function print_list becomes:

    foreach(fun(H) -> io:format(S, "~p~n",[H]) end, L)

    Using the function foreach, the function broadcast becomes:

    foreach(fun(Pid) -> Pid ! M end, L)

    foreach is evaluated for its side-effect and not its value. foreach(Fun ,L) │ │ │ calls Fun(X) for each element X in L and the processing occurs in the │ │ │ order that the elements were defined in L. map does not define the order in │ │ │ which its elements are processed.

    │ │ │ │ │ │ │ │ │ │ │ │ Syntax of Funs │ │ │

    │ │ │

    Funs are written with the following syntax (see │ │ │ -Fun Expressions for full description):

    F = fun (Arg1, Arg2, ... ArgN) ->
    │ │ │ +Fun Expressions for full description):

    F = fun (Arg1, Arg2, ... ArgN) ->
    │ │ │          ...
    │ │ │      end

    This creates an anonymous function of N arguments and binds it to the variable │ │ │ F.

    Another function, FunctionName, written in the same module, can be passed as │ │ │ an argument, using the following syntax:

    F = fun FunctionName/Arity

    With this form of function reference, the function that is referred to does not │ │ │ need to be exported from the module.

    It is also possible to refer to a function defined in a different module, with │ │ │ -the following syntax:

    F = fun Module:FunctionName/Arity

    In this case, the function must be exported from the module in question.

    The following program illustrates the different ways of creating funs:

    -module(fun_test).
    │ │ │ --export([t1/0, t2/0]).
    │ │ │ --import(lists, [map/2]).
    │ │ │ +the following syntax:

    F = fun Module:FunctionName/Arity

    In this case, the function must be exported from the module in question.

    The following program illustrates the different ways of creating funs:

    -module(fun_test).
    │ │ │ +-export([t1/0, t2/0]).
    │ │ │ +-import(lists, [map/2]).
    │ │ │  
    │ │ │ -t1() -> map(fun(X) -> 2 * X end, [1,2,3,4,5]).
    │ │ │ +t1() -> map(fun(X) -> 2 * X end, [1,2,3,4,5]).
    │ │ │  
    │ │ │ -t2() -> map(fun double/1, [1,2,3,4,5]).
    │ │ │ +t2() -> map(fun double/1, [1,2,3,4,5]).
    │ │ │  
    │ │ │ -double(X) -> X * 2.

    The fun F can be evaluated with the following syntax:

    F(Arg1, Arg2, ..., Argn)

    To check whether a term is a fun, use the test │ │ │ -is_function/1 in a guard.

    Example:

    f(F, Args) when is_function(F) ->
    │ │ │ -   apply(F, Args);
    │ │ │ -f(N, _) when is_integer(N) ->
    │ │ │ +double(X) -> X * 2.

    The fun F can be evaluated with the following syntax:

    F(Arg1, Arg2, ..., Argn)

    To check whether a term is a fun, use the test │ │ │ +is_function/1 in a guard.

    Example:

    f(F, Args) when is_function(F) ->
    │ │ │ +   apply(F, Args);
    │ │ │ +f(N, _) when is_integer(N) ->
    │ │ │     N.

    Funs are a distinct type. The BIFs erlang:fun_info/1,2 can be used to retrieve │ │ │ information about a fun, and the BIF erlang:fun_to_list/1 returns a textual │ │ │ representation of a fun. The check_process_code/2 │ │ │ BIF returns true if the process contains funs that depend on the old version │ │ │ of a module.

    │ │ │ │ │ │ │ │ │ │ │ │ Variable Bindings Within a Fun │ │ │

    │ │ │

    The scope rules for variables that occur in funs are as follows:

    • All variables that occur in the head of a fun are assumed to be "fresh" │ │ │ variables.
    • Variables that are defined before the fun, and that occur in function calls or │ │ │ -guard tests within the fun, have the values they had outside the fun.
    • Variables cannot be exported from a fun.

    The following examples illustrate these rules:

    print_list(File, List) ->
    │ │ │ -    {ok, Stream} = file:open(File, write),
    │ │ │ -    foreach(fun(X) -> io:format(Stream,"~p~n",[X]) end, List),
    │ │ │ -    file:close(Stream).

    Here, the variable X, defined in the head of the fun, is a new variable. The │ │ │ +guard tests within the fun, have the values they had outside the fun.

  • Variables cannot be exported from a fun.
  • The following examples illustrate these rules:

    print_list(File, List) ->
    │ │ │ +    {ok, Stream} = file:open(File, write),
    │ │ │ +    foreach(fun(X) -> io:format(Stream,"~p~n",[X]) end, List),
    │ │ │ +    file:close(Stream).

    Here, the variable X, defined in the head of the fun, is a new variable. The │ │ │ variable Stream, which is used within the fun, gets its value from the │ │ │ file:open line.

    As any variable that occurs in the head of a fun is considered a new variable, │ │ │ -it is equally valid to write as follows:

    print_list(File, List) ->
    │ │ │ -    {ok, Stream} = file:open(File, write),
    │ │ │ -    foreach(fun(File) ->
    │ │ │ -                io:format(Stream,"~p~n",[File])
    │ │ │ -            end, List),
    │ │ │ -    file:close(Stream).

    Here, File is used as the new variable instead of X. This is not so wise │ │ │ +it is equally valid to write as follows:

    print_list(File, List) ->
    │ │ │ +    {ok, Stream} = file:open(File, write),
    │ │ │ +    foreach(fun(File) ->
    │ │ │ +                io:format(Stream,"~p~n",[File])
    │ │ │ +            end, List),
    │ │ │ +    file:close(Stream).

    Here, File is used as the new variable instead of X. This is not so wise │ │ │ because code in the fun body cannot refer to the variable File, which is │ │ │ defined outside of the fun. Compiling this example gives the following │ │ │ diagnostic:

    ./FileName.erl:Line: Warning: variable 'File'
    │ │ │        shadowed in 'fun'

    This indicates that the variable File, which is defined inside the fun, │ │ │ collides with the variable File, which is defined outside the fun.

    The rules for importing variables into a fun has the consequence that certain │ │ │ pattern matching operations must be moved into guard expressions and cannot be │ │ │ written in the head of the fun. For example, you might write the following code │ │ │ if you intend the first clause of F to be evaluated when the value of its │ │ │ -argument is Y:

    f(...) ->
    │ │ │ +argument is Y:

    f(...) ->
    │ │ │      Y = ...
    │ │ │ -    map(fun(X) when X == Y ->
    │ │ │ +    map(fun(X) when X == Y ->
    │ │ │               ;
    │ │ │ -           (_) ->
    │ │ │ +           (_) ->
    │ │ │               ...
    │ │ │ -        end, ...)
    │ │ │ -    ...

    instead of writing the following code:

    f(...) ->
    │ │ │ +        end, ...)
    │ │ │ +    ...

    instead of writing the following code:

    f(...) ->
    │ │ │      Y = ...
    │ │ │ -    map(fun(Y) ->
    │ │ │ +    map(fun(Y) ->
    │ │ │               ;
    │ │ │ -           (_) ->
    │ │ │ +           (_) ->
    │ │ │               ...
    │ │ │ -        end, ...)
    │ │ │ +        end, ...)
    │ │ │      ...

    │ │ │ │ │ │ │ │ │ │ │ │ Funs and Module Lists │ │ │

    │ │ │

    The following examples show a dialogue with the Erlang shell. All the higher │ │ │ order functions discussed are exported from the module lists.

    │ │ │ │ │ │ │ │ │ │ │ │ map │ │ │

    │ │ │ -

    lists:map/2 takes a function of one argument and a list of terms:

    map(F, [H|T]) -> [F(H)|map(F, T)];
    │ │ │ -map(F, [])    -> [].

    It returns the list obtained by applying the function to every argument in the │ │ │ +

    lists:map/2 takes a function of one argument and a list of terms:

    map(F, [H|T]) -> [F(H)|map(F, T)];
    │ │ │ +map(F, [])    -> [].

    It returns the list obtained by applying the function to every argument in the │ │ │ list.

    When a new fun is defined in the shell, the value of the fun is printed as │ │ │ -Fun#<erl_eval>:

    > Double = fun(X) -> 2 * X end.
    │ │ │ +Fun#<erl_eval>:

    > Double = fun(X) -> 2 * X end.
    │ │ │  #Fun<erl_eval.6.72228031>
    │ │ │ -> lists:map(Double, [1,2,3,4,5]).
    │ │ │ -[2,4,6,8,10]

    │ │ │ +> lists:map(Double, [1,2,3,4,5]). │ │ │ +[2,4,6,8,10]

    │ │ │ │ │ │ │ │ │ │ │ │ any │ │ │

    │ │ │ -

    lists:any/2 takes a predicate P of one argument and a list of terms:

    any(Pred, [H|T]) ->
    │ │ │ -    case Pred(H) of
    │ │ │ +

    lists:any/2 takes a predicate P of one argument and a list of terms:

    any(Pred, [H|T]) ->
    │ │ │ +    case Pred(H) of
    │ │ │          true  ->  true;
    │ │ │ -        false ->  any(Pred, T)
    │ │ │ +        false ->  any(Pred, T)
    │ │ │      end;
    │ │ │ -any(Pred, []) ->
    │ │ │ +any(Pred, []) ->
    │ │ │      false.

    A predicate is a function that returns true or false. any is true if │ │ │ there is a term X in the list such that P(X) is true.

    A predicate Big(X) is defined, which is true if its argument is greater that │ │ │ -10:

    > Big =  fun(X) -> if X > 10 -> true; true -> false end end.
    │ │ │ +10:

    > Big =  fun(X) -> if X > 10 -> true; true -> false end end.
    │ │ │  #Fun<erl_eval.6.72228031>
    │ │ │ -> lists:any(Big, [1,2,3,4]).
    │ │ │ +> lists:any(Big, [1,2,3,4]).
    │ │ │  false
    │ │ │ -> lists:any(Big, [1,2,3,12,5]).
    │ │ │ +> lists:any(Big, [1,2,3,12,5]).
    │ │ │  true

    │ │ │ │ │ │ │ │ │ │ │ │ all │ │ │

    │ │ │ -

    lists:all/2 has the same arguments as any:

    all(Pred, [H|T]) ->
    │ │ │ -    case Pred(H) of
    │ │ │ -        true  ->  all(Pred, T);
    │ │ │ +

    lists:all/2 has the same arguments as any:

    all(Pred, [H|T]) ->
    │ │ │ +    case Pred(H) of
    │ │ │ +        true  ->  all(Pred, T);
    │ │ │          false ->  false
    │ │ │      end;
    │ │ │ -all(Pred, []) ->
    │ │ │ -    true.

    It is true if the predicate applied to all elements in the list is true.

    > lists:all(Big, [1,2,3,4,12,6]).
    │ │ │ +all(Pred, []) ->
    │ │ │ +    true.

    It is true if the predicate applied to all elements in the list is true.

    > lists:all(Big, [1,2,3,4,12,6]).
    │ │ │  false
    │ │ │ -> lists:all(Big, [12,13,14,15]).
    │ │ │ +> lists:all(Big, [12,13,14,15]).
    │ │ │  true

    │ │ │ │ │ │ │ │ │ │ │ │ foreach │ │ │

    │ │ │ -

    lists:foreach/2 takes a function of one argument and a list of terms:

    foreach(F, [H|T]) ->
    │ │ │ -    F(H),
    │ │ │ -    foreach(F, T);
    │ │ │ -foreach(F, []) ->
    │ │ │ +

    lists:foreach/2 takes a function of one argument and a list of terms:

    foreach(F, [H|T]) ->
    │ │ │ +    F(H),
    │ │ │ +    foreach(F, T);
    │ │ │ +foreach(F, []) ->
    │ │ │      ok.

    The function is applied to each argument in the list. foreach returns ok. It │ │ │ -is only used for its side-effect:

    > lists:foreach(fun(X) -> io:format("~w~n",[X]) end, [1,2,3,4]).
    │ │ │ +is only used for its side-effect:

    > lists:foreach(fun(X) -> io:format("~w~n",[X]) end, [1,2,3,4]).
    │ │ │  1
    │ │ │  2
    │ │ │  3
    │ │ │  4
    │ │ │  ok

    │ │ │ │ │ │ │ │ │ │ │ │ foldl │ │ │

    │ │ │ -

    lists:foldl/3 takes a function of two arguments, an accumulator and a list:

    foldl(F, Accu, [Hd|Tail]) ->
    │ │ │ -    foldl(F, F(Hd, Accu), Tail);
    │ │ │ -foldl(F, Accu, []) -> Accu.

    The function is called with two arguments. The first argument is the successive │ │ │ +

    lists:foldl/3 takes a function of two arguments, an accumulator and a list:

    foldl(F, Accu, [Hd|Tail]) ->
    │ │ │ +    foldl(F, F(Hd, Accu), Tail);
    │ │ │ +foldl(F, Accu, []) -> Accu.

    The function is called with two arguments. The first argument is the successive │ │ │ elements in the list. The second argument is the accumulator. The function must │ │ │ return a new accumulator, which is used the next time the function is called.

    If you have a list of lists L = ["I","like","Erlang"], then you can sum the │ │ │ -lengths of all the strings in L as follows:

    > L = ["I","like","Erlang"].
    │ │ │ -["I","like","Erlang"]
    │ │ │ -10> lists:foldl(fun(X, Sum) -> length(X) + Sum end, 0, L).
    │ │ │ -11

    lists:foldl/3 works like a while loop in an imperative language:

    L =  ["I","like","Erlang"],
    │ │ │ +lengths of all the strings in L as follows:

    > L = ["I","like","Erlang"].
    │ │ │ +["I","like","Erlang"]
    │ │ │ +10> lists:foldl(fun(X, Sum) -> length(X) + Sum end, 0, L).
    │ │ │ +11

    lists:foldl/3 works like a while loop in an imperative language:

    L =  ["I","like","Erlang"],
    │ │ │  Sum = 0,
    │ │ │ -while( L != []){
    │ │ │ -    Sum += length(head(L)),
    │ │ │ -    L = tail(L)
    │ │ │ +while( L != []){
    │ │ │ +    Sum += length(head(L)),
    │ │ │ +    L = tail(L)
    │ │ │  end

    │ │ │ │ │ │ │ │ │ │ │ │ mapfoldl │ │ │

    │ │ │ -

    lists:mapfoldl/3 simultaneously maps and folds over a list:

    mapfoldl(F, Accu0, [Hd|Tail]) ->
    │ │ │ -    {R,Accu1} = F(Hd, Accu0),
    │ │ │ -    {Rs,Accu2} = mapfoldl(F, Accu1, Tail),
    │ │ │ -    {[R|Rs], Accu2};
    │ │ │ -mapfoldl(F, Accu, []) -> {[], Accu}.

    The following example shows how to change all letters in L to upper case and │ │ │ -then count them.

    First the change to upper case:

    > Upcase =  fun(X) when $a =< X,  X =< $z -> X + $A - $a;
    │ │ │ -(X) -> X
    │ │ │ +

    lists:mapfoldl/3 simultaneously maps and folds over a list:

    mapfoldl(F, Accu0, [Hd|Tail]) ->
    │ │ │ +    {R,Accu1} = F(Hd, Accu0),
    │ │ │ +    {Rs,Accu2} = mapfoldl(F, Accu1, Tail),
    │ │ │ +    {[R|Rs], Accu2};
    │ │ │ +mapfoldl(F, Accu, []) -> {[], Accu}.

    The following example shows how to change all letters in L to upper case and │ │ │ +then count them.

    First the change to upper case:

    > Upcase =  fun(X) when $a =< X,  X =< $z -> X + $A - $a;
    │ │ │ +(X) -> X
    │ │ │  end.
    │ │ │  #Fun<erl_eval.6.72228031>
    │ │ │  > Upcase_word =
    │ │ │ -fun(X) ->
    │ │ │ -lists:map(Upcase, X)
    │ │ │ +fun(X) ->
    │ │ │ +lists:map(Upcase, X)
    │ │ │  end.
    │ │ │  #Fun<erl_eval.6.72228031>
    │ │ │ -> Upcase_word("Erlang").
    │ │ │ +> Upcase_word("Erlang").
    │ │ │  "ERLANG"
    │ │ │ -> lists:map(Upcase_word, L).
    │ │ │ -["I","LIKE","ERLANG"]

    Now, the fold and the map can be done at the same time:

    > lists:mapfoldl(fun(Word, Sum) ->
    │ │ │ -{Upcase_word(Word), Sum + length(Word)}
    │ │ │ -end, 0, L).
    │ │ │ -{["I","LIKE","ERLANG"],11}

    │ │ │ +> lists:map(Upcase_word, L). │ │ │ +["I","LIKE","ERLANG"]

    Now, the fold and the map can be done at the same time:

    > lists:mapfoldl(fun(Word, Sum) ->
    │ │ │ +{Upcase_word(Word), Sum + length(Word)}
    │ │ │ +end, 0, L).
    │ │ │ +{["I","LIKE","ERLANG"],11}

    │ │ │ │ │ │ │ │ │ │ │ │ filter │ │ │

    │ │ │

    lists:filter/2 takes a predicate of one argument and a list and returns all elements │ │ │ -in the list that satisfy the predicate:

    filter(F, [H|T]) ->
    │ │ │ -    case F(H) of
    │ │ │ -        true  -> [H|filter(F, T)];
    │ │ │ -        false -> filter(F, T)
    │ │ │ +in the list that satisfy the predicate:

    filter(F, [H|T]) ->
    │ │ │ +    case F(H) of
    │ │ │ +        true  -> [H|filter(F, T)];
    │ │ │ +        false -> filter(F, T)
    │ │ │      end;
    │ │ │ -filter(F, []) -> [].
    > lists:filter(Big, [500,12,2,45,6,7]).
    │ │ │ -[500,12,45]

    Combining maps and filters enables writing of very succinct code. For example, │ │ │ +filter(F, []) -> [].

    > lists:filter(Big, [500,12,2,45,6,7]).
    │ │ │ +[500,12,45]

    Combining maps and filters enables writing of very succinct code. For example, │ │ │ to define a set difference function diff(L1, L2) to be the difference between │ │ │ -the lists L1 and L2, the code can be written as follows:

    diff(L1, L2) ->
    │ │ │ -    filter(fun(X) -> not member(X, L2) end, L1).

    This gives the list of all elements in L1 that are not contained in L2.

    The AND intersection of the list L1 and L2 is also easily defined:

    intersection(L1,L2) -> filter(fun(X) -> member(X,L1) end, L2).

    │ │ │ +the lists L1 and L2, the code can be written as follows:

    diff(L1, L2) ->
    │ │ │ +    filter(fun(X) -> not member(X, L2) end, L1).

    This gives the list of all elements in L1 that are not contained in L2.

    The AND intersection of the list L1 and L2 is also easily defined:

    intersection(L1,L2) -> filter(fun(X) -> member(X,L1) end, L2).

    │ │ │ │ │ │ │ │ │ │ │ │ takewhile │ │ │

    │ │ │

    lists:takewhile/2 takes elements X from a list L as long as the predicate │ │ │ -P(X) is true:

    takewhile(Pred, [H|T]) ->
    │ │ │ -    case Pred(H) of
    │ │ │ -        true  -> [H|takewhile(Pred, T)];
    │ │ │ -        false -> []
    │ │ │ +P(X) is true:

    takewhile(Pred, [H|T]) ->
    │ │ │ +    case Pred(H) of
    │ │ │ +        true  -> [H|takewhile(Pred, T)];
    │ │ │ +        false -> []
    │ │ │      end;
    │ │ │ -takewhile(Pred, []) ->
    │ │ │ -    [].
    > lists:takewhile(Big, [200,500,45,5,3,45,6]).
    │ │ │ -[200,500,45]

    │ │ │ +takewhile(Pred, []) -> │ │ │ + [].

    > lists:takewhile(Big, [200,500,45,5,3,45,6]).
    │ │ │ +[200,500,45]

    │ │ │ │ │ │ │ │ │ │ │ │ dropwhile │ │ │

    │ │ │ -

    lists:dropwhile/2 is the complement of takewhile:

    dropwhile(Pred, [H|T]) ->
    │ │ │ -    case Pred(H) of
    │ │ │ -        true  -> dropwhile(Pred, T);
    │ │ │ -        false -> [H|T]
    │ │ │ +

    lists:dropwhile/2 is the complement of takewhile:

    dropwhile(Pred, [H|T]) ->
    │ │ │ +    case Pred(H) of
    │ │ │ +        true  -> dropwhile(Pred, T);
    │ │ │ +        false -> [H|T]
    │ │ │      end;
    │ │ │ -dropwhile(Pred, []) ->
    │ │ │ -    [].
    > lists:dropwhile(Big, [200,500,45,5,3,45,6]).
    │ │ │ -[5,3,45,6]

    │ │ │ +dropwhile(Pred, []) -> │ │ │ + [].

    > lists:dropwhile(Big, [200,500,45,5,3,45,6]).
    │ │ │ +[5,3,45,6]

    │ │ │ │ │ │ │ │ │ │ │ │ splitwith │ │ │

    │ │ │

    lists:splitwith/2 splits the list L into the two sublists {L1, L2}, where │ │ │ -L = takewhile(P, L) and L2 = dropwhile(P, L):

    splitwith(Pred, L) ->
    │ │ │ -    splitwith(Pred, L, []).
    │ │ │ +L = takewhile(P, L) and L2 = dropwhile(P, L):

    splitwith(Pred, L) ->
    │ │ │ +    splitwith(Pred, L, []).
    │ │ │  
    │ │ │ -splitwith(Pred, [H|T], L) ->
    │ │ │ -    case Pred(H) of
    │ │ │ -        true  -> splitwith(Pred, T, [H|L]);
    │ │ │ -        false -> {reverse(L), [H|T]}
    │ │ │ +splitwith(Pred, [H|T], L) ->
    │ │ │ +    case Pred(H) of
    │ │ │ +        true  -> splitwith(Pred, T, [H|L]);
    │ │ │ +        false -> {reverse(L), [H|T]}
    │ │ │      end;
    │ │ │ -splitwith(Pred, [], L) ->
    │ │ │ -    {reverse(L), []}.
    > lists:splitwith(Big, [200,500,45,5,3,45,6]).
    │ │ │ -{[200,500,45],[5,3,45,6]}

    │ │ │ +splitwith(Pred, [], L) -> │ │ │ + {reverse(L), []}.

    > lists:splitwith(Big, [200,500,45,5,3,45,6]).
    │ │ │ +{[200,500,45],[5,3,45,6]}

    │ │ │ │ │ │ │ │ │ │ │ │ Funs Returning Funs │ │ │

    │ │ │

    So far, only functions that take funs as arguments have been described. More │ │ │ powerful functions, that themselves return funs, can also be written. The │ │ │ following examples illustrate these type of functions.

    │ │ │ │ │ │ │ │ │ │ │ │ Simple Higher Order Functions │ │ │

    │ │ │

    Adder(X) is a function that given X, returns a new function G such that │ │ │ -G(K) returns K + X:

    > Adder = fun(X) -> fun(Y) -> X + Y end end.
    │ │ │ +G(K) returns K + X:

    > Adder = fun(X) -> fun(Y) -> X + Y end end.
    │ │ │  #Fun<erl_eval.6.72228031>
    │ │ │ -> Add6 = Adder(6).
    │ │ │ +> Add6 = Adder(6).
    │ │ │  #Fun<erl_eval.6.72228031>
    │ │ │ -> Add6(10).
    │ │ │ +> Add6(10).
    │ │ │  16

    │ │ │ │ │ │ │ │ │ │ │ │ Infinite Lists │ │ │

    │ │ │ -

    The idea is to write something like:

    -module(lazy).
    │ │ │ --export([ints_from/1]).
    │ │ │ -ints_from(N) ->
    │ │ │ -    fun() ->
    │ │ │ -            [N|ints_from(N+1)]
    │ │ │ -    end.

    Then proceed as follows:

    > XX = lazy:ints_from(1).
    │ │ │ +

    The idea is to write something like:

    -module(lazy).
    │ │ │ +-export([ints_from/1]).
    │ │ │ +ints_from(N) ->
    │ │ │ +    fun() ->
    │ │ │ +            [N|ints_from(N+1)]
    │ │ │ +    end.

    Then proceed as follows:

    > XX = lazy:ints_from(1).
    │ │ │  #Fun<lazy.0.29874839>
    │ │ │ -> XX().
    │ │ │ -[1|#Fun<lazy.0.29874839>]
    │ │ │ -> hd(XX()).
    │ │ │ +> XX().
    │ │ │ +[1|#Fun<lazy.0.29874839>]
    │ │ │ +> hd(XX()).
    │ │ │  1
    │ │ │ -> Y = tl(XX()).
    │ │ │ +> Y = tl(XX()).
    │ │ │  #Fun<lazy.0.29874839>
    │ │ │ -> hd(Y()).
    │ │ │ +> hd(Y()).
    │ │ │  2

    And so on. This is an example of "lazy embedding".

    │ │ │ │ │ │ │ │ │ │ │ │ Parsing │ │ │

    │ │ │ -

    The following examples show parsers of the following type:

    Parser(Toks) -> {ok, Tree, Toks1} | fail

    Toks is the list of tokens to be parsed. A successful parse returns │ │ │ +

    The following examples show parsers of the following type:

    Parser(Toks) -> {ok, Tree, Toks1} | fail

    Toks is the list of tokens to be parsed. A successful parse returns │ │ │ {ok, Tree, Toks1}.

    • Tree is a parse tree.
    • Toks1 is a tail of Tree that contains symbols encountered after the │ │ │ structure that was correctly parsed.

    An unsuccessful parse returns fail.

    The following example illustrates a simple, functional parser that parses the │ │ │ grammar:

    (a | b) & (c | d)

    The following code defines a function pconst(X) in the module funparse, │ │ │ -which returns a fun that parses a list of tokens:

    pconst(X) ->
    │ │ │ -    fun (T) ->
    │ │ │ +which returns a fun that parses a list of tokens:

    pconst(X) ->
    │ │ │ +    fun (T) ->
    │ │ │         case T of
    │ │ │ -           [X|T1] -> {ok, {const, X}, T1};
    │ │ │ +           [X|T1] -> {ok, {const, X}, T1};
    │ │ │             _      -> fail
    │ │ │         end
    │ │ │ -    end.

    This function can be used as follows:

    > P1 = funparse:pconst(a).
    │ │ │ +    end.

    This function can be used as follows:

    > P1 = funparse:pconst(a).
    │ │ │  #Fun<funparse.0.22674075>
    │ │ │ -> P1([a,b,c]).
    │ │ │ -{ok,{const,a},[b,c]}
    │ │ │ -> P1([x,y,z]).
    │ │ │ +> P1([a,b,c]).
    │ │ │ +{ok,{const,a},[b,c]}
    │ │ │ +> P1([x,y,z]).
    │ │ │  fail

    Next, the two higher order functions pand and por are defined. They combine │ │ │ -primitive parsers to produce more complex parsers.

    First pand:

    pand(P1, P2) ->
    │ │ │ -    fun (T) ->
    │ │ │ -        case P1(T) of
    │ │ │ -            {ok, R1, T1} ->
    │ │ │ -                case P2(T1) of
    │ │ │ -                    {ok, R2, T2} ->
    │ │ │ -                        {ok, {'and', R1, R2}};
    │ │ │ +primitive parsers to produce more complex parsers.

    First pand:

    pand(P1, P2) ->
    │ │ │ +    fun (T) ->
    │ │ │ +        case P1(T) of
    │ │ │ +            {ok, R1, T1} ->
    │ │ │ +                case P2(T1) of
    │ │ │ +                    {ok, R2, T2} ->
    │ │ │ +                        {ok, {'and', R1, R2}};
    │ │ │                      fail ->
    │ │ │                          fail
    │ │ │                  end;
    │ │ │              fail ->
    │ │ │                  fail
    │ │ │          end
    │ │ │      end.

    Given a parser P1 for grammar G1, and a parser P2 for grammar G2, │ │ │ pand(P1, P2) returns a parser for the grammar, which consists of sequences of │ │ │ tokens that satisfy G1, followed by sequences of tokens that satisfy G2.

    por(P1, P2) returns a parser for the language described by the grammar G1 or │ │ │ -G2:

    por(P1, P2) ->
    │ │ │ -    fun (T) ->
    │ │ │ -        case P1(T) of
    │ │ │ -            {ok, R, T1} ->
    │ │ │ -                {ok, {'or',1,R}, T1};
    │ │ │ +G2:

    por(P1, P2) ->
    │ │ │ +    fun (T) ->
    │ │ │ +        case P1(T) of
    │ │ │ +            {ok, R, T1} ->
    │ │ │ +                {ok, {'or',1,R}, T1};
    │ │ │              fail ->
    │ │ │ -                case P2(T) of
    │ │ │ -                    {ok, R1, T1} ->
    │ │ │ -                        {ok, {'or',2,R1}, T1};
    │ │ │ +                case P2(T) of
    │ │ │ +                    {ok, R1, T1} ->
    │ │ │ +                        {ok, {'or',2,R1}, T1};
    │ │ │                      fail ->
    │ │ │                          fail
    │ │ │                  end
    │ │ │          end
    │ │ │      end.

    The original problem was to parse the grammar (a | b) & (c | d). The following │ │ │ -code addresses this problem:

    grammar() ->
    │ │ │ -    pand(
    │ │ │ -         por(pconst(a), pconst(b)),
    │ │ │ -         por(pconst(c), pconst(d))).

    The following code adds a parser interface to the grammar:

    parse(List) ->
    │ │ │ -    (grammar())(List).

    The parser can be tested as follows:

    > funparse:parse([a,c]).
    │ │ │ -{ok,{'and',{'or',1,{const,a}},{'or',1,{const,c}}}}
    │ │ │ -> funparse:parse([a,d]).
    │ │ │ -{ok,{'and',{'or',1,{const,a}},{'or',2,{const,d}}}}
    │ │ │ -> funparse:parse([b,c]).
    │ │ │ -{ok,{'and',{'or',2,{const,b}},{'or',1,{const,c}}}}
    │ │ │ -> funparse:parse([b,d]).
    │ │ │ -{ok,{'and',{'or',2,{const,b}},{'or',2,{const,d}}}}
    │ │ │ -> funparse:parse([a,b]).
    │ │ │ +code addresses this problem:

    grammar() ->
    │ │ │ +    pand(
    │ │ │ +         por(pconst(a), pconst(b)),
    │ │ │ +         por(pconst(c), pconst(d))).

    The following code adds a parser interface to the grammar:

    parse(List) ->
    │ │ │ +    (grammar())(List).

    The parser can be tested as follows:

    > funparse:parse([a,c]).
    │ │ │ +{ok,{'and',{'or',1,{const,a}},{'or',1,{const,c}}}}
    │ │ │ +> funparse:parse([a,d]).
    │ │ │ +{ok,{'and',{'or',1,{const,a}},{'or',2,{const,d}}}}
    │ │ │ +> funparse:parse([b,c]).
    │ │ │ +{ok,{'and',{'or',2,{const,b}},{'or',1,{const,c}}}}
    │ │ │ +> funparse:parse([b,d]).
    │ │ │ +{ok,{'and',{'or',2,{const,b}},{'or',2,{const,d}}}}
    │ │ │ +> funparse:parse([a,b]).
    │ │ │  fail
    │ │ │ │ │ │ │ │ │

    │ │ │

    An example of a simple server written in plain Erlang is provided in │ │ │ Overview. The server can be reimplemented using │ │ │ -gen_server, resulting in this callback module:

    -module(ch3).
    │ │ │ --behaviour(gen_server).
    │ │ │ +gen_server, resulting in this callback module:

    -module(ch3).
    │ │ │ +-behaviour(gen_server).
    │ │ │  
    │ │ │ --export([start_link/0]).
    │ │ │ --export([alloc/0, free/1]).
    │ │ │ --export([init/1, handle_call/3, handle_cast/2]).
    │ │ │ +-export([start_link/0]).
    │ │ │ +-export([alloc/0, free/1]).
    │ │ │ +-export([init/1, handle_call/3, handle_cast/2]).
    │ │ │  
    │ │ │ -start_link() ->
    │ │ │ -    gen_server:start_link({local, ch3}, ch3, [], []).
    │ │ │ +start_link() ->
    │ │ │ +    gen_server:start_link({local, ch3}, ch3, [], []).
    │ │ │  
    │ │ │ -alloc() ->
    │ │ │ -    gen_server:call(ch3, alloc).
    │ │ │ +alloc() ->
    │ │ │ +    gen_server:call(ch3, alloc).
    │ │ │  
    │ │ │ -free(Ch) ->
    │ │ │ -    gen_server:cast(ch3, {free, Ch}).
    │ │ │ +free(Ch) ->
    │ │ │ +    gen_server:cast(ch3, {free, Ch}).
    │ │ │  
    │ │ │ -init(_Args) ->
    │ │ │ -    {ok, channels()}.
    │ │ │ +init(_Args) ->
    │ │ │ +    {ok, channels()}.
    │ │ │  
    │ │ │ -handle_call(alloc, _From, Chs) ->
    │ │ │ -    {Ch, Chs2} = alloc(Chs),
    │ │ │ -    {reply, Ch, Chs2}.
    │ │ │ +handle_call(alloc, _From, Chs) ->
    │ │ │ +    {Ch, Chs2} = alloc(Chs),
    │ │ │ +    {reply, Ch, Chs2}.
    │ │ │  
    │ │ │ -handle_cast({free, Ch}, Chs) ->
    │ │ │ -    Chs2 = free(Ch, Chs),
    │ │ │ -    {noreply, Chs2}.

    The code is explained in the next sections.

    │ │ │ +handle_cast({free, Ch}, Chs) -> │ │ │ + Chs2 = free(Ch, Chs), │ │ │ + {noreply, Chs2}.

    The code is explained in the next sections.

    │ │ │ │ │ │ │ │ │ │ │ │ Starting a Gen_Server │ │ │

    │ │ │

    In the example in the previous section, gen_server is started by calling │ │ │ -ch3:start_link():

    start_link() ->
    │ │ │ -    gen_server:start_link({local, ch3}, ch3, [], []) => {ok, Pid}

    start_link/0 calls function gen_server:start_link/4. This function │ │ │ +ch3:start_link():

    start_link() ->
    │ │ │ +    gen_server:start_link({local, ch3}, ch3, [], []) => {ok, Pid}

    start_link/0 calls function gen_server:start_link/4. This function │ │ │ spawns and links to a new process, a gen_server.

    • The first argument, {local, ch3}, specifies the name. │ │ │ The gen_server is then locally registered as ch3.

      If the name is omitted, the gen_server is not registered. Instead its pid │ │ │ must be used. The name can also be given as {global, Name}, in which case │ │ │ the gen_server is registered using global:register_name/2.

    • The second argument, ch3, is the name of the callback module, which is │ │ │ the module where the callback functions are located.

      The interface functions (start_link/0, alloc/0, and free/1) are located │ │ │ in the same module as the callback functions (init/1, handle_call/3, and │ │ │ handle_cast/2). It is usually good programming practice to have the code │ │ │ corresponding to one process contained in a single module.

    • The third argument, [], is a term that is passed as is to the callback │ │ │ function init. Here, init does not need any indata and ignores the │ │ │ argument.

    • The fourth argument, [], is a list of options. See gen_server │ │ │ for the available options.

    If name registration succeeds, the new gen_server process calls the callback │ │ │ function ch3:init([]). init is expected to return {ok, State}, where │ │ │ State is the internal state of the gen_server. In this case, the state is │ │ │ -the available channels.

    init(_Args) ->
    │ │ │ -    {ok, channels()}.

    gen_server:start_link/4 is synchronous. It does not return until the │ │ │ +the available channels.

    init(_Args) ->
    │ │ │ +    {ok, channels()}.

    gen_server:start_link/4 is synchronous. It does not return until the │ │ │ gen_server has been initialized and is ready to receive requests.

    gen_server:start_link/4 must be used if the gen_server is part of │ │ │ a supervision tree, meaning that it was started by a supervisor. There │ │ │ is another function, gen_server:start/4, to start a standalone │ │ │ gen_server that is not part of a supervision tree.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -221,32 +221,32 @@ │ │ │

    │ │ │

    The synchronous request alloc() is implemented using gen_server:call/2:

    alloc() ->
    │ │ │      gen_server:call(ch3, alloc).

    ch3 is the name of the gen_server and must agree with the name │ │ │ used to start it. alloc is the actual request.

    The request is made into a message and sent to the gen_server. │ │ │ When the request is received, the gen_server calls │ │ │ handle_call(Request, From, State), which is expected to return │ │ │ a tuple {reply,Reply,State1}. Reply is the reply that is to be sent back │ │ │ -to the client, and State1 is a new value for the state of the gen_server.

    handle_call(alloc, _From, Chs) ->
    │ │ │ -    {Ch, Chs2} = alloc(Chs),
    │ │ │ -    {reply, Ch, Chs2}.

    In this case, the reply is the allocated channel Ch and the new state is the │ │ │ +to the client, and State1 is a new value for the state of the gen_server.

    handle_call(alloc, _From, Chs) ->
    │ │ │ +    {Ch, Chs2} = alloc(Chs),
    │ │ │ +    {reply, Ch, Chs2}.

    In this case, the reply is the allocated channel Ch and the new state is the │ │ │ set of remaining available channels Chs2.

    Thus, the call ch3:alloc() returns the allocated channel Ch and the │ │ │ gen_server then waits for new requests, now with an updated list of │ │ │ available channels.

    │ │ │ │ │ │ │ │ │ │ │ │ Asynchronous Requests - Cast │ │ │

    │ │ │ -

    The asynchronous request free(Ch) is implemented using gen_server:cast/2:

    free(Ch) ->
    │ │ │ -    gen_server:cast(ch3, {free, Ch}).

    ch3 is the name of the gen_server. {free, Ch} is the actual request.

    The request is made into a message and sent to the gen_server. │ │ │ +

    The asynchronous request free(Ch) is implemented using gen_server:cast/2:

    free(Ch) ->
    │ │ │ +    gen_server:cast(ch3, {free, Ch}).

    ch3 is the name of the gen_server. {free, Ch} is the actual request.

    The request is made into a message and sent to the gen_server. │ │ │ cast, and thus free, then returns ok.

    When the request is received, the gen_server calls │ │ │ handle_cast(Request, State), which is expected to return a tuple │ │ │ -{noreply,State1}. State1 is a new value for the state of the gen_server.

    handle_cast({free, Ch}, Chs) ->
    │ │ │ -    Chs2 = free(Ch, Chs),
    │ │ │ -    {noreply, Chs2}.

    In this case, the new state is the updated list of available channels Chs2. │ │ │ +{noreply,State1}. State1 is a new value for the state of the gen_server.

    handle_cast({free, Ch}, Chs) ->
    │ │ │ +    Chs2 = free(Ch, Chs),
    │ │ │ +    {noreply, Chs2}.

    In this case, the new state is the updated list of available channels Chs2. │ │ │ The gen_server is now ready for new requests.

    │ │ │ │ │ │ │ │ │ │ │ │ Stopping │ │ │

    │ │ │

    │ │ │ @@ -257,69 +257,69 @@ │ │ │

    │ │ │

    If the gen_server is part of a supervision tree, no stop function is needed. │ │ │ The gen_server is automatically terminated by its supervisor. Exactly how │ │ │ this is done is defined by a shutdown strategy │ │ │ set in the supervisor.

    If it is necessary to clean up before termination, the shutdown strategy │ │ │ must be a time-out value and the gen_server must be set to trap exit signals │ │ │ in function init. When ordered to shutdown, the gen_server then calls │ │ │ -the callback function terminate(shutdown, State):

    init(Args) ->
    │ │ │ +the callback function terminate(shutdown, State):

    init(Args) ->
    │ │ │      ...,
    │ │ │ -    process_flag(trap_exit, true),
    │ │ │ +    process_flag(trap_exit, true),
    │ │ │      ...,
    │ │ │ -    {ok, State}.
    │ │ │ +    {ok, State}.
    │ │ │  
    │ │ │  ...
    │ │ │  
    │ │ │ -terminate(shutdown, State) ->
    │ │ │ +terminate(shutdown, State) ->
    │ │ │      %% Code for cleaning up here
    │ │ │      ...
    │ │ │      ok.

    │ │ │ │ │ │ │ │ │ │ │ │ Standalone Gen_Servers │ │ │

    │ │ │

    If the gen_server is not part of a supervision tree, a stop function │ │ │ can be useful, for example:

    ...
    │ │ │ -export([stop/0]).
    │ │ │ +export([stop/0]).
    │ │ │  ...
    │ │ │  
    │ │ │ -stop() ->
    │ │ │ -    gen_server:cast(ch3, stop).
    │ │ │ +stop() ->
    │ │ │ +    gen_server:cast(ch3, stop).
    │ │ │  ...
    │ │ │  
    │ │ │ -handle_cast(stop, State) ->
    │ │ │ -    {stop, normal, State};
    │ │ │ -handle_cast({free, Ch}, State) ->
    │ │ │ +handle_cast(stop, State) ->
    │ │ │ +    {stop, normal, State};
    │ │ │ +handle_cast({free, Ch}, State) ->
    │ │ │      ...
    │ │ │  
    │ │ │  ...
    │ │ │  
    │ │ │ -terminate(normal, State) ->
    │ │ │ +terminate(normal, State) ->
    │ │ │      ok.

    The callback function handling the stop request returns a tuple │ │ │ {stop,normal,State1}, where normal specifies that it is │ │ │ a normal termination and State1 is a new value for the state │ │ │ of the gen_server. This causes the gen_server to call │ │ │ terminate(normal, State1) and then it terminates gracefully.

    │ │ │ │ │ │ │ │ │ │ │ │ Handling Other Messages │ │ │

    │ │ │

    If the gen_server is to be able to receive other messages than requests, │ │ │ the callback function handle_info(Info, State) must be implemented │ │ │ to handle them. Examples of other messages are exit messages, │ │ │ if the gen_server is linked to other processes than the supervisor │ │ │ -and it is trapping exit signals.

    handle_info({'EXIT', Pid, Reason}, State) ->
    │ │ │ +and it is trapping exit signals.

    handle_info({'EXIT', Pid, Reason}, State) ->
    │ │ │      %% Code to handle exits here.
    │ │ │      ...
    │ │ │ -    {noreply, State1}.

    The final function to implement is code_change/3:

    code_change(OldVsn, State, Extra) ->
    │ │ │ +    {noreply, State1}.

    The final function to implement is code_change/3:

    code_change(OldVsn, State, Extra) ->
    │ │ │      %% Code to convert state (and more) during code change.
    │ │ │      ...
    │ │ │ -    {ok, NewState}.
    │ │ │ +
    {ok, NewState}.
    │ │ │ │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ Specifying Included Applications │ │ │

    │ │ │

    Which applications to include is defined by the included_applications key in │ │ │ -the .app file:

    {application, prim_app,
    │ │ │ - [{description, "Tree application"},
    │ │ │ -  {vsn, "1"},
    │ │ │ -  {modules, [prim_app_cb, prim_app_sup, prim_app_server]},
    │ │ │ -  {registered, [prim_app_server]},
    │ │ │ -  {included_applications, [incl_app]},
    │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ -  {mod, {prim_app_cb,[]}},
    │ │ │ -  {env, [{file, "/usr/local/log"}]}
    │ │ │ - ]}.

    │ │ │ +the .app file:

    {application, prim_app,
    │ │ │ + [{description, "Tree application"},
    │ │ │ +  {vsn, "1"},
    │ │ │ +  {modules, [prim_app_cb, prim_app_sup, prim_app_server]},
    │ │ │ +  {registered, [prim_app_server]},
    │ │ │ +  {included_applications, [incl_app]},
    │ │ │ +  {applications, [kernel, stdlib, sasl]},
    │ │ │ +  {mod, {prim_app_cb,[]}},
    │ │ │ +  {env, [{file, "/usr/local/log"}]}
    │ │ │ + ]}.

    │ │ │ │ │ │ │ │ │ │ │ │ Synchronizing Processes during Startup │ │ │

    │ │ │

    The supervisor tree of an included application is started as part of the │ │ │ supervisor tree of the including application. If there is a need for │ │ │ synchronization between processes in the including and included applications, │ │ │ this can be achieved by using start phases.

    Start phases are defined by the start_phases key in the .app file as a list │ │ │ of tuples {Phase,PhaseArgs}, where Phase is an atom and PhaseArgs is a │ │ │ term.

    The value of the mod key of the including application must be set to │ │ │ {application_starter,[Module,StartArgs]}, where Module as usual is the │ │ │ application callback module. StartArgs is a term provided as argument to the │ │ │ -callback function Module:start/2:

    {application, prim_app,
    │ │ │ - [{description, "Tree application"},
    │ │ │ -  {vsn, "1"},
    │ │ │ -  {modules, [prim_app_cb, prim_app_sup, prim_app_server]},
    │ │ │ -  {registered, [prim_app_server]},
    │ │ │ -  {included_applications, [incl_app]},
    │ │ │ -  {start_phases, [{init,[]}, {go,[]}]},
    │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ -  {mod, {application_starter,[prim_app_cb,[]]}},
    │ │ │ -  {env, [{file, "/usr/local/log"}]}
    │ │ │ - ]}.
    │ │ │ +callback function Module:start/2:

    {application, prim_app,
    │ │ │ + [{description, "Tree application"},
    │ │ │ +  {vsn, "1"},
    │ │ │ +  {modules, [prim_app_cb, prim_app_sup, prim_app_server]},
    │ │ │ +  {registered, [prim_app_server]},
    │ │ │ +  {included_applications, [incl_app]},
    │ │ │ +  {start_phases, [{init,[]}, {go,[]}]},
    │ │ │ +  {applications, [kernel, stdlib, sasl]},
    │ │ │ +  {mod, {application_starter,[prim_app_cb,[]]}},
    │ │ │ +  {env, [{file, "/usr/local/log"}]}
    │ │ │ + ]}.
    │ │ │  
    │ │ │ -{application, incl_app,
    │ │ │ - [{description, "Included application"},
    │ │ │ -  {vsn, "1"},
    │ │ │ -  {modules, [incl_app_cb, incl_app_sup, incl_app_server]},
    │ │ │ -  {registered, []},
    │ │ │ -  {start_phases, [{go,[]}]},
    │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ -  {mod, {incl_app_cb,[]}}
    │ │ │ - ]}.

    When starting a primary application with included applications, the primary │ │ │ +{application, incl_app, │ │ │ + [{description, "Included application"}, │ │ │ + {vsn, "1"}, │ │ │ + {modules, [incl_app_cb, incl_app_sup, incl_app_server]}, │ │ │ + {registered, []}, │ │ │ + {start_phases, [{go,[]}]}, │ │ │ + {applications, [kernel, stdlib, sasl]}, │ │ │ + {mod, {incl_app_cb,[]}} │ │ │ + ]}.

    When starting a primary application with included applications, the primary │ │ │ application is started the normal way, that is:

    • The application controller creates an application master for the application
    • The application master calls Module:start(normal, StartArgs) to start the │ │ │ top supervisor.

    Then, for the primary application and each included application in top-down, │ │ │ left-to-right order, the application master calls │ │ │ Module:start_phase(Phase, Type, PhaseArgs) for each phase defined for the │ │ │ primary application, in that order. If a phase is not defined for an included │ │ │ application, the function is not called for this phase and application.

    The following requirements apply to the .app file for an included application:

    • The {mod, {Module,StartArgs}} option must be included. This option is used │ │ │ to find the callback module Module of the application. StartArgs is │ │ │ ignored, as Module:start/2 is called only for the primary application.
    • If the included application itself contains included applications, instead the │ │ │ {mod, {application_starter, [Module,StartArgs]}} option must be included.
    • The {start_phases, [{Phase,PhaseArgs}]} option must be included, and the set │ │ │ of specified phases must be a subset of the set of phases specified for the │ │ │ primary application.

    When starting prim_app as defined above, the application controller calls the │ │ │ following callback functions before application:start(prim_app) returns a │ │ │ -value:

    application:start(prim_app)
    │ │ │ - => prim_app_cb:start(normal, [])
    │ │ │ - => prim_app_cb:start_phase(init, normal, [])
    │ │ │ - => prim_app_cb:start_phase(go, normal, [])
    │ │ │ - => incl_app_cb:start_phase(go, normal, [])
    │ │ │ +value:

    application:start(prim_app)
    │ │ │ + => prim_app_cb:start(normal, [])
    │ │ │ + => prim_app_cb:start_phase(init, normal, [])
    │ │ │ + => prim_app_cb:start_phase(go, normal, [])
    │ │ │ + => incl_app_cb:start_phase(go, normal, [])
    │ │ │  ok
    │ │ │
    │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ Frequently Asked Questions │ │ │

    │ │ │
    • Q: So, now I can build Erlang using GCC on Windows?

      A: No, unfortunately not. You'll need Microsoft's Visual C++ │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/list_comprehensions.html │ │ │ @@ -117,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 integers {A,B,C} such that │ │ │ A**2 + B**2 = C**2 and where the sum of the sides is equal to, or less than, │ │ │ -N:

      pyth(N) ->
      │ │ │ -    [ {A,B,C} ||
      │ │ │ -        A <:- lists:seq(1,N),
      │ │ │ -        B <:- lists:seq(1,N),
      │ │ │ -        C <:- lists:seq(1,N),
      │ │ │ +N:

      pyth(N) ->
      │ │ │ +    [ {A,B,C} ||
      │ │ │ +        A <:- lists:seq(1,N),
      │ │ │ +        B <:- lists:seq(1,N),
      │ │ │ +        C <:- lists:seq(1,N),
      │ │ │          A+B+C =< N,
      │ │ │          A*A+B*B == C*C
      │ │ │ -    ].
      > pyth(3).
      │ │ │ -[].
      │ │ │ -> pyth(11).
      │ │ │ -[].
      │ │ │ -> pyth(12).
      │ │ │ -[{3,4,5},{4,3,5}]
      │ │ │ -> pyth(50).
      │ │ │ -[{3,4,5},
      │ │ │ - {4,3,5},
      │ │ │ - {5,12,13},
      │ │ │ - {6,8,10},
      │ │ │ - {8,6,10},
      │ │ │ - {8,15,17},
      │ │ │ - {9,12,15},
      │ │ │ - {12,5,13},
      │ │ │ - {12,9,15},
      │ │ │ - {12,16,20},
      │ │ │ - {15,8,17},
      │ │ │ - {16,12,20}]

      The following code reduces the search space and is more efficient:

      pyth1(N) ->
      │ │ │ -   [{A,B,C} ||
      │ │ │ -       A <:- lists:seq(1,N-2),
      │ │ │ -       B <:- lists:seq(A+1,N-1),
      │ │ │ -       C <:- lists:seq(B+1,N),
      │ │ │ +    ].
      > pyth(3).
      │ │ │ +[].
      │ │ │ +> pyth(11).
      │ │ │ +[].
      │ │ │ +> pyth(12).
      │ │ │ +[{3,4,5},{4,3,5}]
      │ │ │ +> pyth(50).
      │ │ │ +[{3,4,5},
      │ │ │ + {4,3,5},
      │ │ │ + {5,12,13},
      │ │ │ + {6,8,10},
      │ │ │ + {8,6,10},
      │ │ │ + {8,15,17},
      │ │ │ + {9,12,15},
      │ │ │ + {12,5,13},
      │ │ │ + {12,9,15},
      │ │ │ + {12,16,20},
      │ │ │ + {15,8,17},
      │ │ │ + {16,12,20}]

      The following code reduces the search space and is more efficient:

      pyth1(N) ->
      │ │ │ +   [{A,B,C} ||
      │ │ │ +       A <:- lists:seq(1,N-2),
      │ │ │ +       B <:- lists:seq(A+1,N-1),
      │ │ │ +       C <:- lists:seq(B+1,N),
      │ │ │         A+B+C =< N,
      │ │ │ -       A*A+B*B == C*C ].

      │ │ │ + A*A+B*B == C*C ].

      │ │ │ │ │ │ │ │ │ │ │ │ Simplifications With List Comprehensions │ │ │

      │ │ │

      As an example, list comprehensions can be used to simplify some of the functions │ │ │ -in lists.erl:

      append(L)   ->  [X || L1 <:- L, X <:- L1].
      │ │ │ -map(Fun, L) -> [Fun(X) || X <:- L].
      │ │ │ -filter(Pred, L) -> [X || X <:- L, Pred(X)].
      │ │ │ -zip(L1, L2) -> [{X,Y} || X <:- L1 && Y <:- L2].

      │ │ │ +in lists.erl:

      append(L)   ->  [X || L1 <:- L, X <:- L1].
      │ │ │ +map(Fun, L) -> [Fun(X) || X <:- L].
      │ │ │ +filter(Pred, L) -> [X || X <:- L, Pred(X)].
      │ │ │ +zip(L1, L2) -> [{X,Y} || X <:- L1 && Y <:- L2].

      │ │ │ │ │ │ │ │ │ │ │ │ Variable Bindings in List Comprehensions │ │ │

      │ │ │

      The scope rules for variables that occur in list comprehensions are as follows:

      • All variables that occur in a generator pattern are assumed to be "fresh" │ │ │ variables.
      • Any variables that are defined before the list comprehension, and that are │ │ │ used in filters, have the values they had before the list comprehension.
      • Variables cannot be exported from a list comprehension.
      • Within a zip generator, binding of all variables happen at the same time.

      As an example of these rules, suppose you want to write the function select, │ │ │ which selects certain elements from a list of tuples. Suppose you write │ │ │ select(X, L) -> [Y || {X, Y} <- L]. with the intention of extracting all │ │ │ tuples from L, where the first item is X.

      Compiling this gives the following diagnostic:

      ./FileName.erl:Line: Warning: variable 'X' shadowed in generate

      This diagnostic warns that the variable X in the pattern is not the same as │ │ │ -the variable X that occurs in the function head.

      Evaluating select gives the following result:

      > select(b,[{a,1},{b,2},{c,3},{b,7}]).
      │ │ │ -[1,2,3,7]

      This is not the wanted result. To achieve the desired effect, select must be │ │ │ -written as follows:

      select(X, L) ->  [Y || {X1, Y} <- L, X == X1].

      The generator now contains unbound variables and the test has been moved into │ │ │ -the filter.

      This now works as expected:

      > select(b,[{a,1},{b,2},{c,3},{b,7}]).
      │ │ │ -[2,7]

      Also note that a variable in a generator pattern will shadow a variable with the │ │ │ -same name bound in a previous generator pattern. For example:

      > [{X,Y} || X <- [1,2,3], X=Y <- [a,b,c]].
      │ │ │ -[{a,a},{b,b},{c,c},{a,a},{b,b},{c,c},{a,a},{b,b},{c,c}]

      A consequence of the rules for importing variables into a list comprehensions is │ │ │ +the variable X that occurs in the function head.

      Evaluating select gives the following result:

      > select(b,[{a,1},{b,2},{c,3},{b,7}]).
      │ │ │ +[1,2,3,7]

      This is not the wanted result. To achieve the desired effect, select must be │ │ │ +written as follows:

      select(X, L) ->  [Y || {X1, Y} <- L, X == X1].

      The generator now contains unbound variables and the test has been moved into │ │ │ +the filter.

      This now works as expected:

      > select(b,[{a,1},{b,2},{c,3},{b,7}]).
      │ │ │ +[2,7]

      Also note that a variable in a generator pattern will shadow a variable with the │ │ │ +same name bound in a previous generator pattern. For example:

      > [{X,Y} || X <- [1,2,3], X=Y <- [a,b,c]].
      │ │ │ +[{a,a},{b,b},{c,c},{a,a},{b,b},{c,c},{a,a},{b,b},{c,c}]

      A consequence of the rules for importing variables into a list comprehensions is │ │ │ that certain pattern matching operations must be moved into the filters and │ │ │ -cannot be written directly in the generators.

      To illustrate this, do not write as follows:

      f(...) ->
      │ │ │ +cannot be written directly in the generators.

      To illustrate this, do not write as follows:

      f(...) ->
      │ │ │      Y = ...
      │ │ │ -    [ Expression || PatternInvolving Y  <- Expr, ...]
      │ │ │ -    ...

      Instead, write as follows:

      f(...) ->
      │ │ │ +    [ Expression || PatternInvolving Y  <- Expr, ...]
      │ │ │ +    ...

      Instead, write as follows:

      f(...) ->
      │ │ │      Y = ...
      │ │ │ -    [ Expression || PatternInvolving Y1  <- Expr, Y == Y1, ...]
      │ │ │ +    [ Expression || PatternInvolving Y1  <- Expr, Y == Y1, ...]
      │ │ │      ...

      │ │ │ │ │ │ │ │ │ │ │ │ Strict and Relaxed Generators │ │ │

      │ │ │

      Strict and relaxed generators have different behaviors when the right-hand │ │ │ side expression does not match the left-hand side pattern. A relaxed generator │ │ │ ignores that term and continues on. A strict generator fails with an exception.

      Their difference can be shown in the following example. The generator │ │ │ expects a two-tuple pattern. If a relaxed generator is used, b will be │ │ │ silently skipped. If a strict generator is used, an exception will be raised │ │ │ -when the pattern matching fails with b.

      {_,_} <-  [{ok, a}, b]
      │ │ │ -{_,_} <:- [{ok, a}, b]

      Semantically, strict or relaxed generators convey different intentions from │ │ │ +when the pattern matching fails with b.

      {_,_} <-  [{ok, a}, b]
      │ │ │ +{_,_} <:- [{ok, a}, b]

      Semantically, strict or relaxed generators convey different intentions from │ │ │ the programmer. Strict generators are used when unexpected elements in the │ │ │ input data should not be tolerated. Any element not conforming to specific │ │ │ patterns should immediately crash the comprehension, because the program may │ │ │ not be prepared to handle it.

      For example, the following comprehension is rewritten from one in the Erlang │ │ │ linter. It extracts arities from all defined functions. All elements in the │ │ │ list DefinedFuns are two-tuples, containing name and arity for functions. │ │ │ If any of them differs from this pattern, it means that something has added │ │ │ an invalid item into the list of defined functions. It is better for the linter │ │ │ to crash in the comprehension than skipping the invalid item and continue │ │ │ running. Using a strict generator here is correct, because the linter should │ │ │ -not hide the presence of an internal inconsistency.

      [Arity || {_FunName, Arity} <:- DefinedFuns]

      In contrast, relaxed generators are used when unexpected elements in the input │ │ │ +not hide the presence of an internal inconsistency.

      [Arity || {_FunName, Arity} <:- DefinedFuns]

      In contrast, relaxed generators are used when unexpected elements in the input │ │ │ data should be filtered out. The programmer is aware that some elements │ │ │ may not conform to specific patterns. Those elements can be safely excluded │ │ │ from the comprehension result.

      For example, the following comprehension is from a compiler module that │ │ │ transforms normal Erlang code to Core Erlang. It finds all defined functions │ │ │ from an abstract form, and output them in two-tuples, each containing name and │ │ │ arity of a function. Not all forms are function declarations. All the forms │ │ │ that are not function declarations should be ignored by this comprehensions. │ │ │ Using a relaxed generator here is correct, because the programmer intends to │ │ │ -exclude all elements with other patterns.

      [{Name,Arity} || {function,_,Name,Arity,_} <- Forms]

      Strict and relaxed generators don't always have distinct use cases. When the │ │ │ +exclude all elements with other patterns.

      [{Name,Arity} || {function,_,Name,Arity,_} <- Forms]

      Strict and relaxed generators don't always have distinct use cases. When the │ │ │ left-hand side pattern of a generator is a fresh variable, pattern matching │ │ │ cannot fail. Using either strict or relaxed generators leads to the same │ │ │ behavior. While the preference and use cases might be individual, it is │ │ │ recommended to use strict generators when either can be used. Using strict │ │ │ generators by default aligns with Erlang's "Let it crash" philosophy.

      │ │ │ │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/listhandling.html │ │ │ @@ -120,105 +120,105 @@ │ │ │ │ │ │ │ │ │ Creating a List │ │ │ │ │ │

      Lists can only be built starting from the end and attaching list elements at the │ │ │ beginning. If you use the ++ operator as follows, a new list is created that │ │ │ is a copy of the elements in List1, followed by List2:

      List1 ++ List2

      Looking at how lists:append/2 or ++ would be implemented in plain Erlang, │ │ │ -clearly the first list is copied:

      append([H|T], Tail) ->
      │ │ │ -    [H|append(T, Tail)];
      │ │ │ -append([], Tail) ->
      │ │ │ +clearly the first list is copied:

      append([H|T], Tail) ->
      │ │ │ +    [H|append(T, Tail)];
      │ │ │ +append([], Tail) ->
      │ │ │      Tail.

      When recursing and building a list, it is important to ensure that you attach │ │ │ the new elements to the beginning of the list. In this way, you will build one │ │ │ -list, not hundreds or thousands of copies of the growing result list.

      Let us first see how it is not to be done:

      DO NOT

      bad_fib(N) ->
      │ │ │ -    bad_fib(N, 0, 1, []).
      │ │ │ +list, not hundreds or thousands of copies of the growing result list.

      Let us first see how it is not to be done:

      DO NOT

      bad_fib(N) ->
      │ │ │ +    bad_fib(N, 0, 1, []).
      │ │ │  
      │ │ │ -bad_fib(0, _Current, _Next, Fibs) ->
      │ │ │ +bad_fib(0, _Current, _Next, Fibs) ->
      │ │ │      Fibs;
      │ │ │ -bad_fib(N, Current, Next, Fibs) ->
      │ │ │ -    bad_fib(N - 1, Next, Current + Next, Fibs ++ [Current]).

      Here more than one list is built. In each iteration step a new list is created │ │ │ +bad_fib(N, Current, Next, Fibs) -> │ │ │ + bad_fib(N - 1, Next, Current + Next, Fibs ++ [Current]).

      Here more than one list is built. In each iteration step a new list is created │ │ │ that is one element longer than the new previous list.

      To avoid copying the result in each iteration, build the list in reverse order │ │ │ -and reverse the list when you are done:

      DO

      tail_recursive_fib(N) ->
      │ │ │ -    tail_recursive_fib(N, 0, 1, []).
      │ │ │ +and reverse the list when you are done:

      DO

      tail_recursive_fib(N) ->
      │ │ │ +    tail_recursive_fib(N, 0, 1, []).
      │ │ │  
      │ │ │ -tail_recursive_fib(0, _Current, _Next, Fibs) ->
      │ │ │ -    lists:reverse(Fibs);
      │ │ │ -tail_recursive_fib(N, Current, Next, Fibs) ->
      │ │ │ -    tail_recursive_fib(N - 1, Next, Current + Next, [Current|Fibs]).

      │ │ │ +tail_recursive_fib(0, _Current, _Next, Fibs) -> │ │ │ + lists:reverse(Fibs); │ │ │ +tail_recursive_fib(N, Current, Next, Fibs) -> │ │ │ + tail_recursive_fib(N - 1, Next, Current + Next, [Current|Fibs]).

      │ │ │ │ │ │ │ │ │ │ │ │ List Comprehensions │ │ │

      │ │ │ -

      A list comprehension:

      [Expr(E) || E <- List]

      is basically translated to a local function:

      'lc^0'([E|Tail], Expr) ->
      │ │ │ -    [Expr(E)|'lc^0'(Tail, Expr)];
      │ │ │ -'lc^0'([], _Expr) -> [].

      If the result of the list comprehension will obviously not be used, a list │ │ │ -will not be constructed. For example, in this code:

      [io:put_chars(E) || E <- List],
      │ │ │ +

      A list comprehension:

      [Expr(E) || E <- List]

      is basically translated to a local function:

      'lc^0'([E|Tail], Expr) ->
      │ │ │ +    [Expr(E)|'lc^0'(Tail, Expr)];
      │ │ │ +'lc^0'([], _Expr) -> [].

      If the result of the list comprehension will obviously not be used, a list │ │ │ +will not be constructed. For example, in this code:

      [io:put_chars(E) || E <- List],
      │ │ │  ok.

      or in this code:

      case Var of
      │ │ │      ... ->
      │ │ │ -        [io:put_chars(E) || E <- List];
      │ │ │ +        [io:put_chars(E) || E <- List];
      │ │ │      ... ->
      │ │ │  end,
      │ │ │ -some_function(...),

      the value is not assigned to a variable, not passed to another function, and not │ │ │ +some_function(...),

      the value is not assigned to a variable, not passed to another function, and not │ │ │ returned. This means that there is no need to construct a list and the compiler │ │ │ -will simplify the code for the list comprehension to:

      'lc^0'([E|Tail], Expr) ->
      │ │ │ -    Expr(E),
      │ │ │ -    'lc^0'(Tail, Expr);
      │ │ │ -'lc^0'([], _Expr) -> [].

      The compiler also understands that assigning to _ means that the value will │ │ │ -not be used. Therefore, the code in the following example will also be optimized:

      _ = [io:put_chars(E) || E <- List],
      │ │ │ +will simplify the code for the list comprehension to:

      'lc^0'([E|Tail], Expr) ->
      │ │ │ +    Expr(E),
      │ │ │ +    'lc^0'(Tail, Expr);
      │ │ │ +'lc^0'([], _Expr) -> [].

      The compiler also understands that assigning to _ means that the value will │ │ │ +not be used. Therefore, the code in the following example will also be optimized:

      _ = [io:put_chars(E) || E <- List],
      │ │ │  ok.

      │ │ │ │ │ │ │ │ │ │ │ │ Deep and Flat Lists │ │ │

      │ │ │

      lists:flatten/1 builds an entirely new list. It is therefore expensive, and │ │ │ even more expensive than the ++ operator (which copies its left argument, │ │ │ but not its right argument).

      In the following situations it is unnecessary to call lists:flatten/1:

      • When sending data to a port. Ports understand deep lists so there is no reason │ │ │ to flatten the list before sending it to the port.
      • When calling BIFs that accept deep lists, such as │ │ │ list_to_binary/1 or │ │ │ iolist_to_binary/1.
      • When you know that your list is only one level deep. Use lists:append/1 │ │ │ -instead.

      Examples:

      DO

      port_command(Port, DeepList)

      DO NOT

      port_command(Port, lists:flatten(DeepList))

      A common way to send a zero-terminated string to a port is the following:

      DO NOT

      TerminatedStr = String ++ [0],
      │ │ │ -port_command(Port, TerminatedStr)

      Instead:

      DO

      TerminatedStr = [String, 0],
      │ │ │ -port_command(Port, TerminatedStr)

      DO

      1> lists:append([[1], [2], [3]]).
      │ │ │ -[1,2,3]

      DO NOT

      1> lists:flatten([[1], [2], [3]]).
      │ │ │ -[1,2,3]

      │ │ │ +instead.

    Examples:

    DO

    port_command(Port, DeepList)

    DO NOT

    port_command(Port, lists:flatten(DeepList))

    A common way to send a zero-terminated string to a port is the following:

    DO NOT

    TerminatedStr = String ++ [0],
    │ │ │ +port_command(Port, TerminatedStr)

    Instead:

    DO

    TerminatedStr = [String, 0],
    │ │ │ +port_command(Port, TerminatedStr)

    DO

    1> lists:append([[1], [2], [3]]).
    │ │ │ +[1,2,3]

    DO NOT

    1> lists:flatten([[1], [2], [3]]).
    │ │ │ +[1,2,3]

    │ │ │ │ │ │ │ │ │ │ │ │ Recursive List Functions │ │ │

    │ │ │

    There are two basic ways to write a function that traverses a list and │ │ │ produces a new list.

    The first way is writing a body-recursive function:

    %% Add 42 to each integer in the list.
    │ │ │ -add_42_body([H|T]) ->
    │ │ │ -    [H + 42 | add_42_body(T)];
    │ │ │ -add_42_body([]) ->
    │ │ │ -    [].

    The second way is writing a tail-recursive function:

    %% Add 42 to each integer in the list.
    │ │ │ -add_42_tail(List) ->
    │ │ │ -    add_42_tail(List, []).
    │ │ │ -
    │ │ │ -add_42_tail([H|T], Acc) ->
    │ │ │ -    add_42_tail(T, [H + 42 | Acc]);
    │ │ │ -add_42_tail([], Acc) ->
    │ │ │ -    lists:reverse(Acc).

    In early version of Erlang the tail-recursive function would typically │ │ │ +add_42_body([H|T]) -> │ │ │ + [H + 42 | add_42_body(T)]; │ │ │ +add_42_body([]) -> │ │ │ + [].

    The second way is writing a tail-recursive function:

    %% Add 42 to each integer in the list.
    │ │ │ +add_42_tail(List) ->
    │ │ │ +    add_42_tail(List, []).
    │ │ │ +
    │ │ │ +add_42_tail([H|T], Acc) ->
    │ │ │ +    add_42_tail(T, [H + 42 | Acc]);
    │ │ │ +add_42_tail([], Acc) ->
    │ │ │ +    lists:reverse(Acc).

    In early version of Erlang the tail-recursive function would typically │ │ │ be more efficient. In modern versions of Erlang, there is usually not │ │ │ much difference in performance between a body-recursive list function and │ │ │ tail-recursive function that reverses the list at the end. Therefore, │ │ │ concentrate on writing beautiful code and forget about the performance │ │ │ of your list functions. In the time-critical parts of your code, │ │ │ measure before rewriting your code.

    For a thorough discussion about tail and body recursion, see │ │ │ Erlang's Tail Recursion is Not a Silver Bullet.

    Note

    This section is about list functions that construct lists. A tail-recursive │ │ │ function that does not construct a list runs in constant space, while the │ │ │ corresponding body-recursive function uses stack space proportional to the │ │ │ length of the list.

    For example, a function that sums a list of integers, is not to be written as │ │ │ -follows:

    DO NOT

    recursive_sum([H|T]) -> H+recursive_sum(T);
    │ │ │ -recursive_sum([])    -> 0.

    Instead:

    DO

    sum(L) -> sum(L, 0).
    │ │ │ +follows:

    DO NOT

    recursive_sum([H|T]) -> H+recursive_sum(T);
    │ │ │ +recursive_sum([])    -> 0.

    Instead:

    DO

    sum(L) -> sum(L, 0).
    │ │ │  
    │ │ │ -sum([H|T], Sum) -> sum(T, Sum + H);
    │ │ │ -sum([], Sum)    -> Sum.
    │ │ │ +
    sum([H|T], Sum) -> sum(T, Sum + H); │ │ │ +sum([], Sum) -> Sum.
    │ │ │ │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ File Inclusion │ │ │

    │ │ │ -

    A file can be included as follows:

    -include(File).
    │ │ │ --include_lib(File).

    File, a string, is to point out a file. The contents of this file are included │ │ │ +

    A file can be included as follows:

    -include(File).
    │ │ │ +-include_lib(File).

    File, a string, is to point out a file. The contents of this file are included │ │ │ as is, at the position of the directive.

    Include files are typically used for record and macro definitions that are │ │ │ shared by several modules. It is recommended to use the file name extension │ │ │ .hrl for include files.

    File can start with a path component $VAR, for some string VAR. If that is │ │ │ the case, the value of the environment variable VAR as returned by │ │ │ os:getenv(VAR) is substituted for $VAR. If os:getenv(VAR) returns false, │ │ │ $VAR is left as is.

    If the filename File is absolute (possibly after variable substitution), the │ │ │ include file with that name is included. Otherwise, the specified file is │ │ │ searched for in the following directories, and in this order:

    1. The current working directory
    2. The directory where the module is being compiled
    3. The directories given by the include option

    For details, see erlc in ERTS and │ │ │ -compile in Compiler.

    Examples:

    -include("my_records.hrl").
    │ │ │ --include("incdir/my_records.hrl").
    │ │ │ --include("/home/user/proj/my_records.hrl").
    │ │ │ --include("$PROJ_ROOT/my_records.hrl").

    include_lib is similar to include, but is not to point out an absolute file. │ │ │ +compile in Compiler.

    Examples:

    -include("my_records.hrl").
    │ │ │ +-include("incdir/my_records.hrl").
    │ │ │ +-include("/home/user/proj/my_records.hrl").
    │ │ │ +-include("$PROJ_ROOT/my_records.hrl").

    include_lib is similar to include, but is not to point out an absolute file. │ │ │ Instead, the first path component (possibly after variable substitution) is │ │ │ -assumed to be the name of an application.

    Example:

    -include_lib("kernel/include/file.hrl").

    The code server uses code:lib_dir(kernel) to find the directory of the current │ │ │ +assumed to be the name of an application.

    Example:

    -include_lib("kernel/include/file.hrl").

    The code server uses code:lib_dir(kernel) to find the directory of the current │ │ │ (latest) version of Kernel, and then the subdirectory include is searched for │ │ │ the file file.hrl.

    │ │ │ │ │ │ │ │ │ │ │ │ Defining and Using Macros │ │ │

    │ │ │ -

    A macro is defined as follows:

    -define(Const, Replacement).
    │ │ │ --define(Func(Var1,...,VarN), Replacement).

    A macro definition can be placed anywhere among the attributes and function │ │ │ +

    A macro is defined as follows:

    -define(Const, Replacement).
    │ │ │ +-define(Func(Var1,...,VarN), Replacement).

    A macro definition can be placed anywhere among the attributes and function │ │ │ declarations of a module, but the definition must come before any usage of the │ │ │ macro.

    If a macro is used in several modules, it is recommended that the macro │ │ │ definition is placed in an include file.

    A macro is used as follows:

    ?Const
    │ │ │  ?Func(Arg1,...,ArgN)

    Macros are expanded during compilation. A simple macro ?Const is replaced with │ │ │ -Replacement.

    Example:

    -define(TIMEOUT, 200).
    │ │ │ +Replacement.

    Example:

    -define(TIMEOUT, 200).
    │ │ │  ...
    │ │ │ -call(Request) ->
    │ │ │ -    server:call(refserver, Request, ?TIMEOUT).

    This is expanded to:

    call(Request) ->
    │ │ │ -    server:call(refserver, Request, 200).

    A macro ?Func(Arg1,...,ArgN) is replaced with Replacement, where all │ │ │ +call(Request) -> │ │ │ + server:call(refserver, Request, ?TIMEOUT).

    This is expanded to:

    call(Request) ->
    │ │ │ +    server:call(refserver, Request, 200).

    A macro ?Func(Arg1,...,ArgN) is replaced with Replacement, where all │ │ │ occurrences of a variable Var from the macro definition are replaced with the │ │ │ -corresponding argument Arg.

    Example:

    -define(MACRO1(X, Y), {a, X, b, Y}).
    │ │ │ +corresponding argument Arg.

    Example:

    -define(MACRO1(X, Y), {a, X, b, Y}).
    │ │ │  ...
    │ │ │ -bar(X) ->
    │ │ │ -    ?MACRO1(a, b),
    │ │ │ -    ?MACRO1(X, 123)

    This is expanded to:

    bar(X) ->
    │ │ │ -    {a,a,b,b},
    │ │ │ -    {a,X,b,123}.

    It is good programming practice, but not mandatory, to ensure that a macro │ │ │ +bar(X) -> │ │ │ + ?MACRO1(a, b), │ │ │ + ?MACRO1(X, 123)

    This is expanded to:

    bar(X) ->
    │ │ │ +    {a,a,b,b},
    │ │ │ +    {a,X,b,123}.

    It is good programming practice, but not mandatory, to ensure that a macro │ │ │ definition is a valid Erlang syntactic form.

    To view the result of macro expansion, a module can be compiled with the 'P' │ │ │ option. compile:file(File, ['P']). This produces a listing of the parsed code │ │ │ after preprocessing and parse transforms, in the file File.P.

    │ │ │ │ │ │ │ │ │ │ │ │ Predefined Macros │ │ │ @@ -185,29 +185,29 @@ │ │ │ │ │ │ │ │ │ Macros Overloading │ │ │

    │ │ │

    It is possible to overload macros, except for predefined macros. An overloaded │ │ │ macro has more than one definition, each with a different number of arguments.

    Change

    Support for overloading of macros was added in Erlang 5.7.5/OTP R13B04.

    A macro ?Func(Arg1,...,ArgN) with a (possibly empty) list of arguments results │ │ │ in an error message if there is at least one definition of Func with │ │ │ -arguments, but none with N arguments.

    Assuming these definitions:

    -define(F0(), c).
    │ │ │ --define(F1(A), A).
    │ │ │ --define(C, m:f).

    the following does not work:

    f0() ->
    │ │ │ +arguments, but none with N arguments.

    Assuming these definitions:

    -define(F0(), c).
    │ │ │ +-define(F1(A), A).
    │ │ │ +-define(C, m:f).

    the following does not work:

    f0() ->
    │ │ │      ?F0. % No, an empty list of arguments expected.
    │ │ │  
    │ │ │ -f1(A) ->
    │ │ │ -    ?F1(A, A). % No, exactly one argument expected.

    On the other hand,

    f() ->
    │ │ │ -    ?C().

    is expanded to

    f() ->
    │ │ │ -    m:f().

    │ │ │ +f1(A) -> │ │ │ + ?F1(A, A). % No, exactly one argument expected.

    On the other hand,

    f() ->
    │ │ │ +    ?C().

    is expanded to

    f() ->
    │ │ │ +    m:f().

    │ │ │ │ │ │ │ │ │ │ │ │ Removing a macro definition │ │ │

    │ │ │ -

    A definition of macro can be removed as follows:

    -undef(Macro).

    │ │ │ +

    A definition of macro can be removed as follows:

    -undef(Macro).

    │ │ │ │ │ │ │ │ │ │ │ │ Conditional Compilation │ │ │

    │ │ │

    The following macro directives support conditional compilation:

    • -ifdef(Macro). - Evaluate the following lines only if Macro is │ │ │ defined.

    • -ifndef(Macro). - Evaluate the following lines only if Macro is not │ │ │ @@ -219,43 +219,43 @@ │ │ │ true, and the Condition evaluates to true, the lines following the elif │ │ │ are evaluated instead.

    • -endif. - Specifies the end of a series of control flow directives.

    Note

    Macro directives cannot be used inside functions.

    Syntactically, the Condition in if and elif must be a │ │ │ guard expression. Other constructs (such as │ │ │ a case expression) result in a compilation error.

    As opposed to the standard guard expressions, an expression in an if and │ │ │ elif also supports calling the psuedo-function defined(Name), which tests │ │ │ whether the Name argument is the name of a previously defined macro. │ │ │ defined(Name) evaluates to true if the macro is defined and false │ │ │ -otherwise. An attempt to call other functions results in a compilation error.

    Example:

    -module(m).
    │ │ │ +otherwise. An attempt to call other functions results in a compilation error.

    Example:

    -module(m).
    │ │ │  ...
    │ │ │  
    │ │ │ --ifdef(debug).
    │ │ │ --define(LOG(X), io:format("{~p,~p}: ~p~n", [?MODULE,?LINE,X])).
    │ │ │ +-ifdef(debug).
    │ │ │ +-define(LOG(X), io:format("{~p,~p}: ~p~n", [?MODULE,?LINE,X])).
    │ │ │  -else.
    │ │ │ --define(LOG(X), true).
    │ │ │ +-define(LOG(X), true).
    │ │ │  -endif.
    │ │ │  
    │ │ │  ...

    When trace output is desired, debug is to be defined when the module m is │ │ │ compiled:

    % erlc -Ddebug m.erl
    │ │ │  
    │ │ │  or
    │ │ │  
    │ │ │ -1> c(m, {d, debug}).
    │ │ │ -{ok,m}

    ?LOG(Arg) is then expanded to a call to io:format/2 and provide the user │ │ │ -with some simple trace output.

    Example:

    -module(m)
    │ │ │ +1> c(m, {d, debug}).
    │ │ │ +{ok,m}

    ?LOG(Arg) is then expanded to a call to io:format/2 and provide the user │ │ │ +with some simple trace output.

    Example:

    -module(m)
    │ │ │  ...
    │ │ │ --if(?OTP_RELEASE >= 25).
    │ │ │ +-if(?OTP_RELEASE >= 25).
    │ │ │  %% Code that will work in OTP 25 or higher
    │ │ │ --elif(?OTP_RELEASE >= 26).
    │ │ │ +-elif(?OTP_RELEASE >= 26).
    │ │ │  %% Code that will work in OTP 26 or higher
    │ │ │  -else.
    │ │ │  %% Code that will work in OTP 24 or lower.
    │ │ │  -endif.
    │ │ │  ...

    This code uses the OTP_RELEASE macro to conditionally select code depending on │ │ │ -release.

    Example:

    -module(m)
    │ │ │ +release.

    Example:

    -module(m)
    │ │ │  ...
    │ │ │ --if(?OTP_RELEASE >= 26 andalso defined(debug)).
    │ │ │ +-if(?OTP_RELEASE >= 26 andalso defined(debug)).
    │ │ │  %% Debugging code that requires OTP 26 or later.
    │ │ │  -else.
    │ │ │  %% Non-debug code that works in any release.
    │ │ │  -endif.
    │ │ │  ...

    This code uses the OTP_RELEASE macro and defined(debug) to compile debug │ │ │ code only for OTP 26 or later.

    │ │ │ │ │ │ @@ -270,44 +270,44 @@ │ │ │ used. In practice this means it should appear before any -export(..) or record │ │ │ definitions.

    │ │ │ │ │ │ │ │ │ │ │ │ -error() and -warning() directives │ │ │

    │ │ │ -

    The directive -error(Term) causes a compilation error.

    Example:

    -module(t).
    │ │ │ --export([version/0]).
    │ │ │ +

    The directive -error(Term) causes a compilation error.

    Example:

    -module(t).
    │ │ │ +-export([version/0]).
    │ │ │  
    │ │ │ --ifdef(VERSION).
    │ │ │ -version() -> ?VERSION.
    │ │ │ +-ifdef(VERSION).
    │ │ │ +version() -> ?VERSION.
    │ │ │  -else.
    │ │ │ --error("Macro VERSION must be defined.").
    │ │ │ -version() -> "".
    │ │ │ +-error("Macro VERSION must be defined.").
    │ │ │ +version() -> "".
    │ │ │  -endif.

    The error message will look like this:

    % erlc t.erl
    │ │ │ -t.erl:7: -error("Macro VERSION must be defined.").

    The directive -warning(Term) causes a compilation warning.

    Example:

    -module(t).
    │ │ │ --export([version/0]).
    │ │ │ +t.erl:7: -error("Macro VERSION must be defined.").

    The directive -warning(Term) causes a compilation warning.

    Example:

    -module(t).
    │ │ │ +-export([version/0]).
    │ │ │  
    │ │ │ --ifndef(VERSION).
    │ │ │ --warning("Macro VERSION not defined -- using default version.").
    │ │ │ --define(VERSION, "0").
    │ │ │ +-ifndef(VERSION).
    │ │ │ +-warning("Macro VERSION not defined -- using default version.").
    │ │ │ +-define(VERSION, "0").
    │ │ │  -endif.
    │ │ │ -version() -> ?VERSION.

    The warning message will look like this:

    % erlc t.erl
    │ │ │ +version() -> ?VERSION.

    The warning message will look like this:

    % erlc t.erl
    │ │ │  t.erl:5: Warning: -warning("Macro VERSION not defined -- using default version.").

    Change

    The -error() and -warning() directives were added in Erlang/OTP 19.

    │ │ │ │ │ │ │ │ │ │ │ │ Stringifying Macro Arguments │ │ │

    │ │ │

    The construction ??Arg, where Arg is a macro argument, is expanded to a │ │ │ string containing the tokens of the argument. This is similar to the #arg │ │ │ -stringifying construction in C.

    Example:

    -define(TESTCALL(Call), io:format("Call ~s: ~w~n", [??Call, Call])).
    │ │ │ +stringifying construction in C.

    Example:

    -define(TESTCALL(Call), io:format("Call ~s: ~w~n", [??Call, Call])).
    │ │ │  
    │ │ │ -?TESTCALL(myfunction(1,2)),
    │ │ │ -?TESTCALL(you:function(2,1)).

    results in

    io:format("Call ~s: ~w~n",["myfunction ( 1 , 2 )",myfunction(1,2)]),
    │ │ │ -io:format("Call ~s: ~w~n",["you : function ( 2 , 1 )",you:function(2,1)]).

    That is, a trace output, with both the function called and the resulting value.

    │ │ │ +
    ?TESTCALL(myfunction(1,2)), │ │ │ +?TESTCALL(you:function(2,1)).

    results in

    io:format("Call ~s: ~w~n",["myfunction ( 1 , 2 )",myfunction(1,2)]),
    │ │ │ +io:format("Call ~s: ~w~n",["you : function ( 2 , 1 )",you:function(2,1)]).

    That is, a trace output, with both the function called and the resulting value.

    │ │ │

    │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │
  • maps:get/3 function. If there are default │ │ │ values, sharing of keys between different instances of the map will be less │ │ │ effective, and it is not possible to match multiple elements having default │ │ │ values in one go.

  • To avoid having to deal with a map that may lack some keys, maps:merge/2 can │ │ │ -efficiently add multiple default values. For example:

    DefaultMap = #{shoe_size => 42, editor => emacs},
    │ │ │ -MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)
  • │ │ │ +efficiently add multiple default values. For example:

    DefaultMap = #{shoe_size => 42, editor => emacs},
    │ │ │ +MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)

    │ │ │ │ │ │ │ │ │ │ │ │ Using Maps as Dictionaries │ │ │

    │ │ │

    Using a map as a dictionary implies the following usage pattern:

    • Keys are usually variables not known at compile-time.
    • There can be any number of elements in the map.
    • Usually, no more than one element is looked up or updated at once.

    Given that usage pattern, the difference in performance between using the map │ │ │ syntax and the maps module is usually small. Therefore, which one to use is │ │ │ @@ -167,18 +167,18 @@ │ │ │ choice.

    │ │ │ │ │ │ │ │ │ │ │ │ Using Maps as Sets │ │ │

    │ │ │

    Starting in OTP 24, the sets module has an option to represent sets as maps. │ │ │ -Examples:

    1> sets:new([{version,2}]).
    │ │ │ -#{}
    │ │ │ -2> sets:from_list([x,y,z], [{version,2}]).
    │ │ │ -#{x => [],y => [],z => []}

    sets backed by maps is generally the most efficient set representation, with a │ │ │ +Examples:

    1> sets:new([{version,2}]).
    │ │ │ +#{}
    │ │ │ +2> sets:from_list([x,y,z], [{version,2}]).
    │ │ │ +#{x => [],y => [],z => []}

    sets backed by maps is generally the most efficient set representation, with a │ │ │ few possible exceptions:

    • ordsets:intersection/2 can be more efficient than sets:intersection/2. If │ │ │ the intersection operation is frequently used and operations that operate on a │ │ │ single element in a set (such as is_element/2) are avoided, ordsets can │ │ │ be a better choice than sets.
    • If the intersection operation is frequently used and operations that operate │ │ │ on a single element in a set (such as is_element/2) must also be efficient, │ │ │ gb_sets can potentially be a better choice than sets.
    • If the elements of the set are integers in a fairly compact range, the set can │ │ │ be represented as an integer where each bit represents an element in the set. │ │ │ @@ -203,18 +203,18 @@ │ │ │ for the runtime system).

    • N - The number of elements in the map.

    • Keys - A tuple with keys of the map: {Key1,...,KeyN}. The keys are │ │ │ sorted.

    • Value1 - The value corresponding to the first key in the key tuple.

    • ValueN - The value corresponding to the last key in the key tuple.

    As an example, let us look at how the map #{a => foo, z => bar} is │ │ │ represented:

    01234
    FLATMAP2{a,z}foobar

    Table: #{a => foo, z => bar}

    Let us update the map: M#{q => baz}. The map now looks like this:

    012345
    FLATMAP3{a,q,z}foobazbar

    Table: #{a => foo, q => baz, z => bar}

    Finally, change the value of one element: M#{z := bird}. The map now looks │ │ │ like this:

    012345
    FLATMAP3{a,q,z}foobazbird

    Table: #{a => foo, q => baz, z => bird}

    When the value for an existing key is updated, the key tuple is not updated, │ │ │ allowing the key tuple to be shared with other instances of the map that have │ │ │ the same keys. In fact, the key tuple can be shared between all maps with the │ │ │ same keys with some care. To arrange that, define a function that returns a map. │ │ │ -For example:

    new() ->
    │ │ │ -    #{a => default, b => default, c => default}.

    Defined like this, the key tuple {a,b,c} will be a global literal. To ensure │ │ │ +For example:

    new() ->
    │ │ │ +    #{a => default, b => default, c => default}.

    Defined like this, the key tuple {a,b,c} will be a global literal. To ensure │ │ │ that the key tuple is shared when creating an instance of the map, always call │ │ │ -new() and modify the returned map:

        (SOME_MODULE:new())#{a := 42}.

    Using the map syntax with small maps is particularly efficient. As long as the │ │ │ +new() and modify the returned map:

        (SOME_MODULE:new())#{a := 42}.

    Using the map syntax with small maps is particularly efficient. As long as the │ │ │ keys are known at compile-time, the map is updated in one go, making the time to │ │ │ update a map essentially constant regardless of the number of keys updated. The │ │ │ same goes for matching. (When the keys are variables, one or more of the keys │ │ │ could be identical, so the operations need to be performed sequentially from │ │ │ left to right.)

    The memory size for a small map is the size of all keys and values plus 5 words. │ │ │ See Memory for more information about memory sizes.

    │ │ │ │ │ │ @@ -241,21 +241,21 @@ │ │ │ │ │ │ │ │ │ │ │ │ Using the Map Syntax │ │ │

    │ │ │

    Using the map syntax is usually slightly more efficient than using the │ │ │ corresponding function in the maps module.

    The gain in efficiency for the map syntax is more noticeable for the following │ │ │ -operations that can only be achieved using the map syntax:

    • Matching multiple literal keys
    • Updating multiple literal keys
    • Adding multiple literal keys to a map

    For example:

    DO

    Map = Map1#{x := X, y := Y, z := Z}

    DO NOT

    Map2 = maps:update(x, X, Map1),
    │ │ │ -Map3 = maps:update(y, Y, Map2),
    │ │ │ -Map = maps:update(z, Z, Map3)

    If the map is a small map, the first example runs roughly three times as fast.

    Note that for variable keys, the elements are updated sequentially from left to │ │ │ -right. For example, given the following update with variable keys:

    Map = Map1#{Key1 := X, Key2 := Y, Key3 := Z}

    the compiler rewrites it like this to ensure that the updates are applied from │ │ │ -left to right:

    Map2 = Map1#{Key1 := X},
    │ │ │ -Map3 = Map2#{Key2 := Y},
    │ │ │ -Map = Map3#{Key3 := Z}

    If a key is known to exist in a map, using the := operator is slightly more │ │ │ +operations that can only be achieved using the map syntax:

    • Matching multiple literal keys
    • Updating multiple literal keys
    • Adding multiple literal keys to a map

    For example:

    DO

    Map = Map1#{x := X, y := Y, z := Z}

    DO NOT

    Map2 = maps:update(x, X, Map1),
    │ │ │ +Map3 = maps:update(y, Y, Map2),
    │ │ │ +Map = maps:update(z, Z, Map3)

    If the map is a small map, the first example runs roughly three times as fast.

    Note that for variable keys, the elements are updated sequentially from left to │ │ │ +right. For example, given the following update with variable keys:

    Map = Map1#{Key1 := X, Key2 := Y, Key3 := Z}

    the compiler rewrites it like this to ensure that the updates are applied from │ │ │ +left to right:

    Map2 = Map1#{Key1 := X},
    │ │ │ +Map3 = Map2#{Key2 := Y},
    │ │ │ +Map = Map3#{Key3 := Z}

    If a key is known to exist in a map, using the := operator is slightly more │ │ │ efficient than using the => operator for a small map.

    │ │ │ │ │ │ │ │ │ │ │ │ Using the Functions in the maps Module │ │ │

    │ │ │

    Here follows some notes about most of the functions in the maps module. For │ │ │ @@ -306,23 +306,23 @@ │ │ │ │ │ │ │ │ │ │ │ │ maps:get/3 │ │ │ │ │ │

    As an optimization, the compiler will rewrite a call to maps:get/3 to Erlang │ │ │ code similar to the following:

    Result = case Map of
    │ │ │ -             #{Key := Value} -> Value;
    │ │ │ -             #{} -> Default
    │ │ │ +             #{Key := Value} -> Value;
    │ │ │ +             #{} -> Default
    │ │ │           end

    This is reasonably efficient, but if a small map is used as an alternative to │ │ │ using a record it is often better not to rely on default values as it prevents │ │ │ sharing of keys, which may in the end use more memory than what you save from │ │ │ not storing default values in the map.

    If default values are nevertheless required, instead of calling maps:get/3 │ │ │ multiple times, consider putting the default values in a map and merging that │ │ │ -map with the other map:

    DefaultMap = #{Key1 => Value2, Key2 => Value2, ..., KeyN => ValueN},
    │ │ │ -MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)

    This helps share keys between the default map and the one you applied defaults │ │ │ +map with the other map:

    DefaultMap = #{Key1 => Value2, Key2 => Value2, ..., KeyN => ValueN},
    │ │ │ +MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)

    This helps share keys between the default map and the one you applied defaults │ │ │ to, as long as the default map contains all the keys that will ever be used │ │ │ and not just the ones with default values. Whether this is faster than calling │ │ │ maps:get/3 multiple times depends on the size of the map and the number of │ │ │ default values.

    Change

    Before OTP 26.0 maps:get/3 was implemented by calling the function instead │ │ │ of rewriting it as an Erlang expression. It is now slightly faster but can no │ │ │ longer be traced.

    │ │ │ │ │ │ @@ -410,29 +410,29 @@ │ │ │ │ │ │ │ │ │ │ │ │ maps:put/3 │ │ │

    │ │ │

    maps:put/3 is implemented in C.

    If the key is known to already exist in the map, maps:update/3 is slightly │ │ │ more efficient than maps:put/3.

    If the compiler can determine that the third argument is always a map, it │ │ │ -will rewrite the call to maps:put/3 to use the map syntax for updating the map.

    For example, consider the following function:

    add_to_known_map(Map0, A, B, C) when is_map(Map0) ->
    │ │ │ -    Map1 = maps:put(a, A, Map0),
    │ │ │ -    Map2 = maps:put(b, B, Map1),
    │ │ │ -    maps:put(c, C, Map2).

    The compiler first rewrites each call to maps:put/3 to use the map │ │ │ +will rewrite the call to maps:put/3 to use the map syntax for updating the map.

    For example, consider the following function:

    add_to_known_map(Map0, A, B, C) when is_map(Map0) ->
    │ │ │ +    Map1 = maps:put(a, A, Map0),
    │ │ │ +    Map2 = maps:put(b, B, Map1),
    │ │ │ +    maps:put(c, C, Map2).

    The compiler first rewrites each call to maps:put/3 to use the map │ │ │ syntax, and subsequently combines the three update operations to a │ │ │ -single update operation:

    add_to_known_map(Map0, A, B, C) when is_map(Map0) ->
    │ │ │ -    Map0#{a => A, b => B, c => C}.

    If the compiler cannot determine that the third argument is always a │ │ │ +single update operation:

    add_to_known_map(Map0, A, B, C) when is_map(Map0) ->
    │ │ │ +    Map0#{a => A, b => B, c => C}.

    If the compiler cannot determine that the third argument is always a │ │ │ map, it retains the maps:put/3 call. For example, given this │ │ │ -function:

    add_to_map(Map0, A, B, C) ->
    │ │ │ -    Map1 = maps:put(a, A, Map0),
    │ │ │ -    Map2 = maps:put(b, B, Map1),
    │ │ │ -    maps:put(c, C, Map2).

    the compiler keeps the first call to maps:put/3, but rewrites │ │ │ -and combines the other two calls:

    add_to_map(Map0, A, B, C) ->
    │ │ │ -    Map1 = maps:put(a, A, Map0),
    │ │ │ -    Map1#{b => B, c => C}.

    Change

    The rewriting of maps:put/3 to the map syntax was introduced in │ │ │ +function:

    add_to_map(Map0, A, B, C) ->
    │ │ │ +    Map1 = maps:put(a, A, Map0),
    │ │ │ +    Map2 = maps:put(b, B, Map1),
    │ │ │ +    maps:put(c, C, Map2).

    the compiler keeps the first call to maps:put/3, but rewrites │ │ │ +and combines the other two calls:

    add_to_map(Map0, A, B, C) ->
    │ │ │ +    Map1 = maps:put(a, A, Map0),
    │ │ │ +    Map1#{b => B, c => C}.

    Change

    The rewriting of maps:put/3 to the map syntax was introduced in │ │ │ Erlang/OTP 28.

    │ │ │ │ │ │ │ │ │ │ │ │ maps:remove/2 │ │ │

    │ │ │

    maps:remove/2 is implemented in C.

    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/modules.html │ │ │ @@ -118,20 +118,20 @@ │ │ │

    │ │ │ │ │ │ │ │ │ │ │ │ Module Syntax │ │ │

    │ │ │

    Erlang code is divided into modules. A module consists of a sequence of │ │ │ -attributes and function declarations, each terminated by a period (.).

    Example:

    -module(m).          % module attribute
    │ │ │ --export([fact/1]).   % module attribute
    │ │ │ +attributes and function declarations, each terminated by a period (.).

    Example:

    -module(m).          % module attribute
    │ │ │ +-export([fact/1]).   % module attribute
    │ │ │  
    │ │ │ -fact(N) when N>0 ->  % beginning of function declaration
    │ │ │ -    N * fact(N-1);   %  |
    │ │ │ -fact(0) ->           %  |
    │ │ │ +fact(N) when N>0 ->  % beginning of function declaration
    │ │ │ +    N * fact(N-1);   %  |
    │ │ │ +fact(0) ->           %  |
    │ │ │      1.               % end of function declaration

    For a description of function declarations, see │ │ │ Function Declaration Syntax.

    │ │ │ │ │ │ │ │ │ │ │ │ Module Attributes │ │ │

    │ │ │ @@ -176,71 +176,71 @@ │ │ │ meaning.

    │ │ │ │ │ │ │ │ │ │ │ │ Behaviour Module Attribute │ │ │

    │ │ │

    It is possible to specify that the module is the callback module for a │ │ │ -behaviour:

    -behaviour(Behaviour).

    The atom Behaviour gives the name of the behaviour, which can be a │ │ │ +behaviour:

    -behaviour(Behaviour).

    The atom Behaviour gives the name of the behaviour, which can be a │ │ │ user-defined behaviour or one of the following OTP standard behaviours:

    • gen_server
    • gen_statem
    • gen_event
    • supervisor

    The spelling behavior is also accepted.

    The callback functions of the module can be specified either directly by the │ │ │ -exported function behaviour_info/1:

    behaviour_info(callbacks) -> Callbacks.

    or by a -callback attribute for each callback function:

    -callback Name(Arguments) -> Result.

    Here, Arguments is a list of zero or more arguments. The -callback attribute │ │ │ +exported function behaviour_info/1:

    behaviour_info(callbacks) -> Callbacks.

    or by a -callback attribute for each callback function:

    -callback Name(Arguments) -> Result.

    Here, Arguments is a list of zero or more arguments. The -callback attribute │ │ │ is to be preferred since the extra type information can be used by tools to │ │ │ produce documentation or find discrepancies.

    Read more about behaviours and callback modules in │ │ │ OTP Design Principles.

    │ │ │ │ │ │ │ │ │ │ │ │ Record Definitions │ │ │

    │ │ │ -

    The same syntax as for module attributes is used for record definitions:

    -record(Record, Fields).

    Record definitions are allowed anywhere in a module, also among the function │ │ │ +

    The same syntax as for module attributes is used for record definitions:

    -record(Record, Fields).

    Record definitions are allowed anywhere in a module, also among the function │ │ │ declarations. Read more in Records.

    │ │ │ │ │ │ │ │ │ │ │ │ Preprocessor │ │ │

    │ │ │

    The same syntax as for module attributes is used by the preprocessor, which │ │ │ -supports file inclusion, macros, and conditional compilation:

    -include("SomeFile.hrl").
    │ │ │ --define(Macro, Replacement).

    Read more in Preprocessor.

    │ │ │ +supports file inclusion, macros, and conditional compilation:

    -include("SomeFile.hrl").
    │ │ │ +-define(Macro, Replacement).

    Read more in Preprocessor.

    │ │ │ │ │ │ │ │ │ │ │ │ Setting File and Line │ │ │

    │ │ │

    The same syntax as for module attributes is used for changing the pre-defined │ │ │ -macros ?FILE and ?LINE:

    -file(File, Line).

    This attribute is used by tools, such as Yecc, to inform the compiler that the │ │ │ +macros ?FILE and ?LINE:

    -file(File, Line).

    This attribute is used by tools, such as Yecc, to inform the compiler that the │ │ │ source program is generated by another tool. It also indicates the │ │ │ correspondence of source files to lines of the original user-written file, from │ │ │ which the source program is produced.

    │ │ │ │ │ │ │ │ │ │ │ │ Types and function specifications │ │ │

    │ │ │

    A similar syntax as for module attributes is used for specifying types and │ │ │ -function specifications:

    -type my_type() :: atom() | integer().
    │ │ │ --spec my_function(integer()) -> integer().

    Read more in Types and Function specifications.

    The description is based on │ │ │ +function specifications:

    -type my_type() :: atom() | integer().
    │ │ │ +-spec my_function(integer()) -> integer().

    Read more in Types and Function specifications.

    The description is based on │ │ │ EEP8 - Types and function specifications, │ │ │ which is not to be further updated.

    │ │ │ │ │ │ │ │ │ │ │ │ Documentation attributes │ │ │

    │ │ │

    The module attribute -doc(Documentation) is used to provide user documentation │ │ │ -for a function/type/callback:

    -doc("Example documentation").
    │ │ │ -example() -> ok.

    The attribute should be placed just before the entity it documents.The │ │ │ +for a function/type/callback:

    -doc("Example documentation").
    │ │ │ +example() -> ok.

    The attribute should be placed just before the entity it documents.The │ │ │ parenthesis are optional around Documentation. The allowed values for │ │ │ Documentation are:

    • literal string or │ │ │ utf-8 encoded binary string - The string │ │ │ documenting the entity. Any literal string is allowed, so both │ │ │ triple quoted strings and │ │ │ sigils that translate to literal strings can be used. │ │ │ -The following examples are equivalent:

      -doc("Example \"docs\"").
      │ │ │ --doc(<<"Example \"docs\""/utf8>>).
      │ │ │ +The following examples are equivalent:

      -doc("Example \"docs\"").
      │ │ │ +-doc(<<"Example \"docs\""/utf8>>).
      │ │ │  -doc ~S/Example "docs"/.
      │ │ │  -doc """
      │ │ │     Example "docs"
      │ │ │     """
      │ │ │  -doc ~B|Example "docs"|.

      For clarity it is recommended to use either normal "strings" or triple │ │ │ quoted strings for documentation attributes.

    • {file, file:name/0 } - Read the contents of filename and use │ │ │ that as the documentation string.

    • false - Set the current entity as hidden, that is, it should not be │ │ │ @@ -253,15 +253,15 @@ │ │ │ │ │ │ │ │ │ │ │ │ The feature directive │ │ │ │ │ │

      While not a module attribute, but rather a directive (since it might affect │ │ │ syntax), there is the -feature(..) directive used for enabling and disabling │ │ │ -features.

      The syntax is similar to that of an attribute, but has two arguments:

      -feature(FeatureName, enable | disable).

      Note that the feature directive can only appear │ │ │ +features.

      The syntax is similar to that of an attribute, but has two arguments:

      -feature(FeatureName, enable | disable).

      Note that the feature directive can only appear │ │ │ in a prefix of the module.

      │ │ │ │ │ │ │ │ │ │ │ │ Comments │ │ │

      │ │ │

      Comments can be placed anywhere in a module except within strings and │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/nif.html │ │ │ @@ -133,26 +133,26 @@ │ │ │ Erlang Program │ │ │ │ │ │

      Even if all functions of a module are NIFs, an Erlang module is still needed for │ │ │ two reasons:

      • The NIF library must be explicitly loaded by Erlang code in the same module.
      • All NIFs of a module must have an Erlang implementation as well.

      Normally these are minimal stub implementations that throw an exception. But │ │ │ they can also be used as fallback implementations for functions that do not have │ │ │ native implementations on some architectures.

      NIF libraries are loaded by calling erlang:load_nif/2, with the name of the │ │ │ shared library as argument. The second argument can be any term that will be │ │ │ -passed on to the library and used for initialization:

      -module(complex6).
      │ │ │ --export([foo/1, bar/1]).
      │ │ │ --nifs([foo/1, bar/1]).
      │ │ │ --on_load(init/0).
      │ │ │ -
      │ │ │ -init() ->
      │ │ │ -    ok = erlang:load_nif("./complex6_nif", 0).
      │ │ │ -
      │ │ │ -foo(_X) ->
      │ │ │ -    erlang:nif_error(nif_library_not_loaded).
      │ │ │ -bar(_Y) ->
      │ │ │ -    erlang:nif_error(nif_library_not_loaded).

      Here, the directive on_load is used to get function init to be automatically │ │ │ +passed on to the library and used for initialization:

      -module(complex6).
      │ │ │ +-export([foo/1, bar/1]).
      │ │ │ +-nifs([foo/1, bar/1]).
      │ │ │ +-on_load(init/0).
      │ │ │ +
      │ │ │ +init() ->
      │ │ │ +    ok = erlang:load_nif("./complex6_nif", 0).
      │ │ │ +
      │ │ │ +foo(_X) ->
      │ │ │ +    erlang:nif_error(nif_library_not_loaded).
      │ │ │ +bar(_Y) ->
      │ │ │ +    erlang:nif_error(nif_library_not_loaded).

      Here, the directive on_load is used to get function init to be automatically │ │ │ called when the module is loaded. If init returns anything other than ok, │ │ │ such when the loading of the NIF library fails in this example, the module is │ │ │ unloaded and calls to functions within it, fail.

      Loading the NIF library overrides the stub implementations and cause calls to │ │ │ foo and bar to be dispatched to the NIF implementations instead.

      │ │ │ │ │ │ │ │ │ │ │ │ @@ -209,23 +209,23 @@ │ │ │ │ │ │ │ │ │ │ │ │ Running the Example │ │ │

      │ │ │

      Step 1. Compile the C code:

      unix> gcc -o complex6_nif.so -fpic -shared complex.c complex6_nif.c
      │ │ │  windows> cl -LD -MD -Fe complex6_nif.dll complex.c complex6_nif.c

      Step 2: Start Erlang and compile the Erlang code:

      > erl
      │ │ │ -Erlang R13B04 (erts-5.7.5) [64-bit] [smp:4:4] [rq:4] [async-threads:0] [kernel-poll:false]
      │ │ │ +Erlang R13B04 (erts-5.7.5) [64-bit] [smp:4:4] [rq:4] [async-threads:0] [kernel-poll:false]
      │ │ │  
      │ │ │ -Eshell V5.7.5  (abort with ^G)
      │ │ │ -1> c(complex6).
      │ │ │ -{ok,complex6}

      Step 3: Run the example:

      3> complex6:foo(3).
      │ │ │ +Eshell V5.7.5  (abort with ^G)
      │ │ │ +1> c(complex6).
      │ │ │ +{ok,complex6}

      Step 3: Run the example:

      3> complex6:foo(3).
      │ │ │  4
      │ │ │ -4> complex6:bar(5).
      │ │ │ +4> complex6:bar(5).
      │ │ │  10
      │ │ │ -5> complex6:foo("not an integer").
      │ │ │ +5> complex6:foo("not an integer").
      │ │ │  ** exception error: bad argument
      │ │ │       in function  complex6:foo/1
      │ │ │          called as comlpex6:foo("not an integer")
      │ │ │
    │ │ │ │ │ │
    │ │ │
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/nominals.html │ │ │ @@ -123,55 +123,55 @@ │ │ │ │ │ │

    For user-defined types │ │ │ defined with -type, the Erlang compiler will ignore their type names. This │ │ │ means the Erlang compiler uses a structural type system. Two types are seen as │ │ │ equivalent if their structures are the same. Type comparison is based on the │ │ │ structures of the types, not on how the user explicitly defines them. In the │ │ │ following example, meter() and foot() are equivalent, and neither differs │ │ │ -from the basic type integer().

    -type meter() :: integer().
    │ │ │ --type foot() :: integer().

    Nominal typing is an alternative type system. Two nominal types are equivalent │ │ │ +from the basic type integer().

    -type meter() :: integer().
    │ │ │ +-type foot() :: integer().

    Nominal typing is an alternative type system. Two nominal types are equivalent │ │ │ if and only if they are declared with the same type name. The syntax for │ │ │ declaring nominal types is -nominal.

    If meter() and foot() are defined as nominal types, they will no longer be │ │ │ compatible. When a function expects type meter(), passing in type foot() │ │ │ -will result in a warning raised by the type checker.

    -nominal meter() :: integer().
    │ │ │ --nominal foot() :: integer().

    The main use case of nominal types is to prevent accidental misuse of types with │ │ │ +will result in a warning raised by the type checker.

    -nominal meter() :: integer().
    │ │ │ +-nominal foot() :: integer().

    The main use case of nominal types is to prevent accidental misuse of types with │ │ │ the same structure. Within OTP, nominal type-checking is done in Dialyzer. The │ │ │ Erlang compiler does not perform nominal type-checking.

    │ │ │ │ │ │ │ │ │ │ │ │ Nominal Type-Checking Rules │ │ │

    │ │ │

    In general, if two nominal types have different names, and one is not derived │ │ │ from the other, they are not compatible. Dialyzer's nominal type-checking │ │ │ -aligns with the examples' expected results in this section.

    If we continue from the example above:

    -spec int_to_meter(integer()) -> meter().
    │ │ │ -int_to_meter(X) -> X.
    │ │ │ +aligns with the examples' expected results in this section.

    If we continue from the example above:

    -spec int_to_meter(integer()) -> meter().
    │ │ │ +int_to_meter(X) -> X.
    │ │ │  
    │ │ │ --spec foo() -> foot().
    │ │ │ -foo() -> int_to_meter(24).

    A type checker that performs nominal type-checking should raise a warning. │ │ │ +-spec foo() -> foot(). │ │ │ +foo() -> int_to_meter(24).

    A type checker that performs nominal type-checking should raise a warning. │ │ │ According to the specification, foo/0 should return a foot() type. However, │ │ │ the function int_to_meter/1 returns a meter() type, so foo/0 will also │ │ │ return a meter() type. Because meter() and foot() are incompatible │ │ │ nominal types, Dialyzer raises the following warning for foo/0:

    Invalid type specification for function foo/0.
    │ │ │ -The success typing is foo() -> (meter() :: integer())
    │ │ │ -But the spec is foo() -> foot()
    │ │ │ +The success typing is foo() -> (meter() :: integer())
    │ │ │ +But the spec is foo() -> foot()
    │ │ │  The return types do not overlap

    On the other hand, a nominal type is compatible with a non-opaque, non-nominal │ │ │ type with the same structure. This compatibility goes both ways, meaning that │ │ │ passing a structural type when a nominal type is expected is allowed, and │ │ │ -vice versa.

    -spec qaz() -> integer().
    │ │ │ -qaz() -> int_to_meter(24).

    A type checker that performs nominal type-checking should not raise a warning │ │ │ +vice versa.

    -spec qaz() -> integer().
    │ │ │ +qaz() -> int_to_meter(24).

    A type checker that performs nominal type-checking should not raise a warning │ │ │ in this case. The specification says that qaz/0 should return an integer() │ │ │ type. However, the function int_to_meter/1 returns a meter() type, so │ │ │ qaz/0 will also return a meter() type. integer() is not a nominal type. │ │ │ The structure of meter() is compatible with integer(). Dialyzer can │ │ │ analyze the function above without raising a warning.

    There is one exception where two nominal types with different names can be │ │ │ compatible: when one is derived from the other. For nominal types s() and │ │ │ -t(), s() can be derived from t() in the two following ways:

    1. If s() is directly derived from t().
    -nominal s() :: t().
    1. If s() is derived from other nominal types, which are derived from t().
    -nominal s() :: nominal_1().
    │ │ │ --nominal nominal_1() :: nominal_2().
    │ │ │ --nominal nominal_2() :: t().

    In both cases, s() and t() are compatible nominal types even though they │ │ │ +t(), s() can be derived from t() in the two following ways:

    1. If s() is directly derived from t().
    -nominal s() :: t().
    1. If s() is derived from other nominal types, which are derived from t().
    -nominal s() :: nominal_1().
    │ │ │ +-nominal nominal_1() :: nominal_2().
    │ │ │ +-nominal nominal_2() :: t().

    In both cases, s() and t() are compatible nominal types even though they │ │ │ have different names. Defining them in different modules does not affect │ │ │ compatiblity.

    In summary, nominal type-checking rules are as follows:

    A function that has a -spec that states an argument or a return type to be │ │ │ nominal type a/0 (or any other arity), accepts or may return:

    • Nominal type a/0
    • A compatible nominal type b/0
    • A compatible structural type

    A function that has a -spec that states an argument or a return type to be a │ │ │ structural type b/0 (or any other arity), accepts or may return:

    • A compatible structural type
    • A compatible nominal type

    When deciding if a type should be nominal, here are some suggestions:

    • If there are other types in the same module with the same structure, and they │ │ │ should never be mixed, all of them can benefit from being nominal types.
    • If a type represents a unit like meter, second, byte, and so on, defining it │ │ │ as a nominal type is always more useful than -type. You get the nice │ │ │ guarantee that you cannot mix them up with other units defined as nominal │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/opaques.html │ │ │ @@ -124,24 +124,24 @@ │ │ │

      The main use case for opacity in Erlang is to hide the implementation of a data │ │ │ type, enabling evolving the API while minimizing the risk of breaking consumers. │ │ │ The runtime does not check opacity. Dialyzer provides some opacity-checking, but │ │ │ the rest is up to convention.

      Change

      Since Erlang/OTP 28, Dialyzer checks opaques in their defining module in the │ │ │ same way as nominals. Outside of the defining module, Dialyzer checks │ │ │ opaques for opacity violations.

      This document explains what Erlang opacity is (and the trade-offs involved) via │ │ │ the example of the sets:set() data type. This type was │ │ │ -defined in the sets module like this:

      -opaque set(Element) :: #set{segs :: segs(Element)}.

      OTP 24 changed the definition to the following in │ │ │ -this commit.

      -opaque set(Element) :: #set{segs :: segs(Element)} | #{Element => ?VALUE}.

      And this change was safer and more backwards-compatible than if the type had │ │ │ +defined in the sets module like this:

      -opaque set(Element) :: #set{segs :: segs(Element)}.

      OTP 24 changed the definition to the following in │ │ │ +this commit.

      -opaque set(Element) :: #set{segs :: segs(Element)} | #{Element => ?VALUE}.

      And this change was safer and more backwards-compatible than if the type had │ │ │ been defined with -type instead of -opaque. Here is why: when a module │ │ │ defines an -opaque, the contract is that only the defining module should rely │ │ │ on the definition of the type: no other modules should rely on the definition.

      This means that code that pattern-matched on set as a record/tuple technically │ │ │ broke the contract, and opted in to being potentially broken when the definition │ │ │ of set() changed. Before OTP 24, this code printed ok. In OTP 24 it may │ │ │ -error:

      case sets:new() of
      │ │ │ -    Set when is_tuple(Set) ->
      │ │ │ -        io:format("ok")
      │ │ │ +error:

      case sets:new() of
      │ │ │ +    Set when is_tuple(Set) ->
      │ │ │ +        io:format("ok")
      │ │ │  end.

      When working with an opaque defined in another module, here are some │ │ │ recommendations:

      • Don't examine the underlying type using pattern-matching, guards, or functions │ │ │ that reveal the type, such as tuple_size/1 . One exception │ │ │ is that =:= and =/= can be used between two opaques with the same name, or │ │ │ between an opaque and any(), as those comparisons do not reveal underlying │ │ │ types.
      • Use functions provided by the module for working with the type. For │ │ │ example, sets module provides sets:new/0, sets:add_element/2, │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/otp-patch-apply.html │ │ │ @@ -201,15 +201,15 @@ │ │ │ │ │ │ Sanity check │ │ │ │ │ │

        The application dependencies can be checked using the Erlang shell. │ │ │ Application dependencies are verified among installed applications by │ │ │ otp_patch_apply, but these are not necessarily those actually loaded. │ │ │ By calling system_information:sanity_check() one can validate │ │ │ -dependencies among applications actually loaded.

        1> system_information:sanity_check().
        │ │ │ +dependencies among applications actually loaded.

        1> system_information:sanity_check().
        │ │ │  ok

        Please take a look at the reference of sanity_check() for more │ │ │ information.

        │ │ │
    │ │ │ │ │ │
    │ │ │
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/patterns.html │ │ │ @@ -128,18 +128,18 @@ │ │ │ succeeds, any unbound variables in the pattern become bound. If the matching │ │ │ fails, an exception is raised.

    Examples:

    1> X.
    │ │ │  ** 1:1: variable 'X' is unbound **
    │ │ │  2> X = 2.
    │ │ │  2
    │ │ │  3> X + 1.
    │ │ │  3
    │ │ │ -4> {X, Y} = {1, 2}.
    │ │ │ +4> {X, Y} = {1, 2}.
    │ │ │  ** exception error: no match of right hand side value {1,2}
    │ │ │ -5> {X, Y} = {2, 3}.
    │ │ │ -{2,3}
    │ │ │ +5> {X, Y} = {2, 3}.
    │ │ │ +{2,3}
    │ │ │  6> Y.
    │ │ │  3
    │ │ │
    │ │ │ │ │ │
    │ │ │
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/prog_ex_records.html │ │ │ @@ -122,105 +122,105 @@ │ │ │ Records and Tuples │ │ │ │ │ │

    The main advantage of using records rather than tuples is that fields in a │ │ │ record are accessed by name, whereas fields in a tuple are accessed by position. │ │ │ To illustrate these differences, suppose that you want to represent a person │ │ │ with the tuple {Name, Address, Phone}.

    To write functions that manipulate this data, remember the following:

    • The Name field is the first element of the tuple.
    • The Address field is the second element.
    • The Phone field is the third element.

    For example, to extract data from a variable P that contains such a tuple, you │ │ │ can write the following code and then use pattern matching to extract the │ │ │ -relevant fields:

    Name = element(1, P),
    │ │ │ -Address = element(2, P),
    │ │ │ +relevant fields:

    Name = element(1, P),
    │ │ │ +Address = element(2, P),
    │ │ │  ...

    Such code is difficult to read and understand, and errors occur if the numbering │ │ │ of the elements in the tuple is wrong. If the data representation of the fields │ │ │ is changed, by re-ordering, adding, or removing fields, all references to the │ │ │ person tuple must be checked and possibly modified.

    Records allow references to the fields by name, instead of by position. In the │ │ │ -following example, a record instead of a tuple is used to store the data:

    -record(person, {name, phone, address}).

    This enables references to the fields of the record by name. For example, if P │ │ │ +following example, a record instead of a tuple is used to store the data:

    -record(person, {name, phone, address}).

    This enables references to the fields of the record by name. For example, if P │ │ │ is a variable whose value is a person record, the following code access the │ │ │ name and address fields of the records:

    Name = P#person.name,
    │ │ │  Address = P#person.address,
    │ │ │ -...

    Internally, records are represented using tagged tuples:

    {person, Name, Phone, Address}

    │ │ │ +...

    Internally, records are represented using tagged tuples:

    {person, Name, Phone, Address}

    │ │ │ │ │ │ │ │ │ │ │ │ Defining a Record │ │ │

    │ │ │

    This following definition of a person is used in several examples in this │ │ │ section. Three fields are included, name, phone, and address. The default │ │ │ values for name and phone is "" and [], respectively. The default value for │ │ │ address is the atom undefined, since no default value is supplied for this │ │ │ -field:

    -record(person, {name = "", phone = [], address}).

    The record must be defined in the shell to enable use of the record syntax in │ │ │ -the examples:

    > rd(person, {name = "", phone = [], address}).
    │ │ │ +field:

    -record(person, {name = "", phone = [], address}).

    The record must be defined in the shell to enable use of the record syntax in │ │ │ +the examples:

    > rd(person, {name = "", phone = [], address}).
    │ │ │  person

    This is because record definitions are only available at compile time, not at │ │ │ runtime. For details on records in the shell, see the shell manual page in │ │ │ STDLIB.

    │ │ │ │ │ │ │ │ │ │ │ │ Creating a Record │ │ │

    │ │ │ -

    A new person record is created as follows:

    > #person{phone=[0,8,2,3,4,3,1,2], name="Robert"}.
    │ │ │ -#person{name = "Robert",phone = [0,8,2,3,4,3,1,2],address = undefined}

    As the address field was omitted, its default value is used.

    From Erlang 5.1/OTP R8B, a value to all fields in a record can be set with the │ │ │ -special field _. _ means "all fields not explicitly specified".

    Example:

    > #person{name = "Jakob", _ = '_'}.
    │ │ │ -#person{name = "Jakob",phone = '_',address = '_'}

    It is primarily intended to be used in ets:match/2 and │ │ │ +

    A new person record is created as follows:

    > #person{phone=[0,8,2,3,4,3,1,2], name="Robert"}.
    │ │ │ +#person{name = "Robert",phone = [0,8,2,3,4,3,1,2],address = undefined}

    As the address field was omitted, its default value is used.

    From Erlang 5.1/OTP R8B, a value to all fields in a record can be set with the │ │ │ +special field _. _ means "all fields not explicitly specified".

    Example:

    > #person{name = "Jakob", _ = '_'}.
    │ │ │ +#person{name = "Jakob",phone = '_',address = '_'}

    It is primarily intended to be used in ets:match/2 and │ │ │ mnesia:match_object/3, to set record fields to the atom '_'. (This is a │ │ │ wildcard in ets:match/2.)

    │ │ │ │ │ │ │ │ │ │ │ │ Accessing a Record Field │ │ │

    │ │ │ -

    The following example shows how to access a record field:

    > P = #person{name = "Joe", phone = [0,8,2,3,4,3,1,2]}.
    │ │ │ -#person{name = "Joe",phone = [0,8,2,3,4,3,1,2],address = undefined}
    │ │ │ +

    The following example shows how to access a record field:

    > P = #person{name = "Joe", phone = [0,8,2,3,4,3,1,2]}.
    │ │ │ +#person{name = "Joe",phone = [0,8,2,3,4,3,1,2],address = undefined}
    │ │ │  > P#person.name.
    │ │ │  "Joe"

    │ │ │ │ │ │ │ │ │ │ │ │ Updating a Record │ │ │

    │ │ │ -

    The following example shows how to update a record:

    > P1 = #person{name="Joe", phone=[1,2,3], address="A street"}.
    │ │ │ -#person{name = "Joe",phone = [1,2,3],address = "A street"}
    │ │ │ -> P2 = P1#person{name="Robert"}.
    │ │ │ -#person{name = "Robert",phone = [1,2,3],address = "A street"}

    │ │ │ +

    The following example shows how to update a record:

    > P1 = #person{name="Joe", phone=[1,2,3], address="A street"}.
    │ │ │ +#person{name = "Joe",phone = [1,2,3],address = "A street"}
    │ │ │ +> P2 = P1#person{name="Robert"}.
    │ │ │ +#person{name = "Robert",phone = [1,2,3],address = "A street"}

    │ │ │ │ │ │ │ │ │ │ │ │ Type Testing │ │ │

    │ │ │

    The following example shows that the guard succeeds if P is record of type │ │ │ -person:

    foo(P) when is_record(P, person) -> a_person;
    │ │ │ -foo(_) -> not_a_person.

    │ │ │ +person:

    foo(P) when is_record(P, person) -> a_person;
    │ │ │ +foo(_) -> not_a_person.

    │ │ │ │ │ │ │ │ │ │ │ │ Pattern Matching │ │ │

    │ │ │

    Matching can be used in combination with records, as shown in the following │ │ │ -example:

    > P3 = #person{name="Joe", phone=[0,0,7], address="A street"}.
    │ │ │ -#person{name = "Joe",phone = [0,0,7],address = "A street"}
    │ │ │ -> #person{name = Name} = P3, Name.
    │ │ │ +example:

    > P3 = #person{name="Joe", phone=[0,0,7], address="A street"}.
    │ │ │ +#person{name = "Joe",phone = [0,0,7],address = "A street"}
    │ │ │ +> #person{name = Name} = P3, Name.
    │ │ │  "Joe"

    The following function takes a list of person records and searches for the │ │ │ -phone number of a person with a particular name:

    find_phone([#person{name=Name, phone=Phone} | _], Name) ->
    │ │ │ -    {found,  Phone};
    │ │ │ -find_phone([_| T], Name) ->
    │ │ │ -    find_phone(T, Name);
    │ │ │ -find_phone([], Name) ->
    │ │ │ +phone number of a person with a particular name:

    find_phone([#person{name=Name, phone=Phone} | _], Name) ->
    │ │ │ +    {found,  Phone};
    │ │ │ +find_phone([_| T], Name) ->
    │ │ │ +    find_phone(T, Name);
    │ │ │ +find_phone([], Name) ->
    │ │ │      not_found.

    The fields referred to in the pattern can be given in any order.

    │ │ │ │ │ │ │ │ │ │ │ │ Nested Records │ │ │

    │ │ │

    The value of a field in a record can be an instance of a record. Retrieval of │ │ │ nested data can be done stepwise, or in a single step, as shown in the following │ │ │ -example:

    -record(name, {first = "Robert", last = "Ericsson"}).
    │ │ │ --record(person, {name = #name{}, phone}).
    │ │ │ +example:

    -record(name, {first = "Robert", last = "Ericsson"}).
    │ │ │ +-record(person, {name = #name{}, phone}).
    │ │ │  
    │ │ │ -demo() ->
    │ │ │ -  P = #person{name= #name{first="Robert",last="Virding"}, phone=123},
    │ │ │ -  First = (P#person.name)#name.first.

    Here, demo() evaluates to "Robert".

    │ │ │ +demo() -> │ │ │ + P = #person{name= #name{first="Robert",last="Virding"}, phone=123}, │ │ │ + First = (P#person.name)#name.first.

    Here, demo() evaluates to "Robert".

    │ │ │ │ │ │ │ │ │ │ │ │ A Longer Example │ │ │

    │ │ │

    Comments are embedded in the following example:

    %% File: person.hrl
    │ │ │  
    │ │ │ @@ -230,48 +230,48 @@
    │ │ │  %%    name:  A string (default is undefined).
    │ │ │  %%    age:   An integer (default is undefined).
    │ │ │  %%    phone: A list of integers (default is []).
    │ │ │  %%    dict:  A dictionary containing various information
    │ │ │  %%           about the person.
    │ │ │  %%           A {Key, Value} list (default is the empty list).
    │ │ │  %%------------------------------------------------------------
    │ │ │ --record(person, {name, age, phone = [], dict = []}).
    -module(person).
    │ │ │ --include("person.hrl").
    │ │ │ --compile(export_all). % For test purposes only.
    │ │ │ +-record(person, {name, age, phone = [], dict = []}).
    -module(person).
    │ │ │ +-include("person.hrl").
    │ │ │ +-compile(export_all). % For test purposes only.
    │ │ │  
    │ │ │  %% This creates an instance of a person.
    │ │ │  %%   Note: The phone number is not supplied so the
    │ │ │  %%         default value [] will be used.
    │ │ │  
    │ │ │ -make_hacker_without_phone(Name, Age) ->
    │ │ │ -   #person{name = Name, age = Age,
    │ │ │ -           dict = [{computer_knowledge, excellent},
    │ │ │ -                   {drinks, coke}]}.
    │ │ │ +make_hacker_without_phone(Name, Age) ->
    │ │ │ +   #person{name = Name, age = Age,
    │ │ │ +           dict = [{computer_knowledge, excellent},
    │ │ │ +                   {drinks, coke}]}.
    │ │ │  
    │ │ │  %% This demonstrates matching in arguments
    │ │ │  
    │ │ │ -print(#person{name = Name, age = Age,
    │ │ │ -              phone = Phone, dict = Dict}) ->
    │ │ │ -  io:format("Name: ~s, Age: ~w, Phone: ~w ~n"
    │ │ │ -            "Dictionary: ~w.~n", [Name, Age, Phone, Dict]).
    │ │ │ +print(#person{name = Name, age = Age,
    │ │ │ +              phone = Phone, dict = Dict}) ->
    │ │ │ +  io:format("Name: ~s, Age: ~w, Phone: ~w ~n"
    │ │ │ +            "Dictionary: ~w.~n", [Name, Age, Phone, Dict]).
    │ │ │  
    │ │ │  %% Demonstrates type testing, selector, updating.
    │ │ │  
    │ │ │ -birthday(P) when is_record(P, person) ->
    │ │ │ -   P#person{age = P#person.age + 1}.
    │ │ │ +birthday(P) when is_record(P, person) ->
    │ │ │ +   P#person{age = P#person.age + 1}.
    │ │ │  
    │ │ │ -register_two_hackers() ->
    │ │ │ -   Hacker1 = make_hacker_without_phone("Joe", 29),
    │ │ │ -   OldHacker = birthday(Hacker1),
    │ │ │ +register_two_hackers() ->
    │ │ │ +   Hacker1 = make_hacker_without_phone("Joe", 29),
    │ │ │ +   OldHacker = birthday(Hacker1),
    │ │ │     % The central_register_server should have
    │ │ │     % an interface function for this.
    │ │ │ -   central_register_server ! {register_person, Hacker1},
    │ │ │ -   central_register_server ! {register_person,
    │ │ │ -             OldHacker#person{name = "Robert",
    │ │ │ -                              phone = [0,8,3,2,4,5,3,1]}}.
    │ │ │ +
    central_register_server ! {register_person, Hacker1}, │ │ │ + central_register_server ! {register_person, │ │ │ + OldHacker#person{name = "Robert", │ │ │ + phone = [0,8,3,2,4,5,3,1]}}.
    │ │ │
    │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ Header Files │ │ │

    │ │ │

    As shown above, some files have extension .hrl. These are header files that │ │ │ -are included in the .erl files by:

    -include("File_Name").

    for example:

    -include("mess_interface.hrl").

    In the case above the file is fetched from the same directory as all the other │ │ │ +are included in the .erl files by:

    -include("File_Name").

    for example:

    -include("mess_interface.hrl").

    In the case above the file is fetched from the same directory as all the other │ │ │ files in the messenger example. (manual).

    .hrl files can contain any valid Erlang code but are most often used for record │ │ │ and macro definitions.

    │ │ │ │ │ │ │ │ │ │ │ │ Records │ │ │

    │ │ │ -

    A record is defined as:

    -record(name_of_record,{field_name1, field_name2, field_name3, ......}).

    For example:

    -record(message_to,{to_name, message}).

    This is equivalent to:

    {message_to, To_Name, Message}

    Creating a record is best illustrated by an example:

    #message_to{message="hello", to_name=fred)

    This creates:

    {message_to, fred, "hello"}

    Notice that you do not have to worry about the order you assign values to the │ │ │ +

    A record is defined as:

    -record(name_of_record,{field_name1, field_name2, field_name3, ......}).

    For example:

    -record(message_to,{to_name, message}).

    This is equivalent to:

    {message_to, To_Name, Message}

    Creating a record is best illustrated by an example:

    #message_to{message="hello", to_name=fred)

    This creates:

    {message_to, fred, "hello"}

    Notice that you do not have to worry about the order you assign values to the │ │ │ various parts of the records when you create it. The advantage of using records │ │ │ is that by placing their definitions in header files you can conveniently define │ │ │ interfaces that are easy to change. For example, if you want to add a new field │ │ │ to the record, you only have to change the code where the new field is used and │ │ │ not at every place the record is referred to. If you leave out a field when │ │ │ creating a record, it gets the value of the atom undefined. (manual)

    Pattern matching with records is very similar to creating records. For example, │ │ │ -inside a case or receive:

    #message_to{to_name=ToName, message=Message} ->

    This is the same as:

    {message_to, ToName, Message}

    │ │ │ +inside a case or receive:

    #message_to{to_name=ToName, message=Message} ->

    This is the same as:

    {message_to, ToName, Message}

    │ │ │ │ │ │ │ │ │ │ │ │ Macros │ │ │

    │ │ │

    Another thing that has been added to the messenger is a macro. The file │ │ │ mess_config.hrl contains the definition:

    %%% Configure the location of the server node,
    │ │ │ --define(server_node, messenger@super).

    This file is included in mess_server.erl:

    -include("mess_config.hrl").

    Every occurrence of ?server_node in mess_server.erl is now replaced by │ │ │ -messenger@super.

    A macro is also used when spawning the server process:

    spawn(?MODULE, server, [])

    This is a standard macro (that is, defined by the system, not by the user). │ │ │ +-define(server_node, messenger@super).

    This file is included in mess_server.erl:

    -include("mess_config.hrl").

    Every occurrence of ?server_node in mess_server.erl is now replaced by │ │ │ +messenger@super.

    A macro is also used when spawning the server process:

    spawn(?MODULE, server, [])

    This is a standard macro (that is, defined by the system, not by the user). │ │ │ ?MODULE is always replaced by the name of the current module (that is, the │ │ │ -module definition near the start of the file). There are more advanced ways │ │ │ of using macros with, for example, parameters.

    The three Erlang (.erl) files in the messenger example are individually │ │ │ compiled into object code file (.beam). The Erlang system loads and links │ │ │ these files into the system when they are referred to during execution of the │ │ │ code. In this case, they are simply put in our current working directory (that │ │ │ is, the place you have done "cd" to). There are ways of putting the .beam │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/ref_man_functions.html │ │ │ @@ -120,51 +120,51 @@ │ │ │ │ │ │ │ │ │ Function Declaration Syntax │ │ │ │ │ │

    A function declaration is a sequence of function clauses separated by │ │ │ semicolons, and terminated by a period (.).

    A function clause consists of a clause head and a clause body, separated by │ │ │ ->.

    A clause head consists of the function name, an argument list, and an optional │ │ │ -guard sequence beginning with the keyword when:

    Name(Pattern11,...,Pattern1N) [when GuardSeq1] ->
    │ │ │ +guard sequence beginning with the keyword when:

    Name(Pattern11,...,Pattern1N) [when GuardSeq1] ->
    │ │ │      Body1;
    │ │ │  ...;
    │ │ │ -Name(PatternK1,...,PatternKN) [when GuardSeqK] ->
    │ │ │ +Name(PatternK1,...,PatternKN) [when GuardSeqK] ->
    │ │ │      BodyK.

    The function name is an atom. Each argument is a pattern.

    The number of arguments N is the arity of the function. A function is │ │ │ uniquely defined by the module name, function name, and arity. That is, two │ │ │ functions with the same name and in the same module, but with different arities │ │ │ are two different functions.

    A function named f in module mod and with arity N is often denoted as │ │ │ mod:f/N.

    A clause body consists of a sequence of expressions separated by comma (,):

    Expr1,
    │ │ │  ...,
    │ │ │  ExprN

    Valid Erlang expressions and guard sequences are described in │ │ │ -Expressions.

    Example:

    fact(N) when N > 0 ->  % first clause head
    │ │ │ -    N * fact(N-1);     % first clause body
    │ │ │ +Expressions.

    Example:

    fact(N) when N > 0 ->  % first clause head
    │ │ │ +    N * fact(N-1);     % first clause body
    │ │ │  
    │ │ │ -fact(0) ->             % second clause head
    │ │ │ +fact(0) ->             % second clause head
    │ │ │      1.                 % second clause body

    │ │ │ │ │ │ │ │ │ │ │ │ Function Evaluation │ │ │

    │ │ │

    When a function M:F/N is called, first the code for the function is located. │ │ │ If the function cannot be found, an undef runtime error occurs. Notice that │ │ │ the function must be exported to be visible outside the module it is defined in.

    If the function is found, the function clauses are scanned sequentially until a │ │ │ clause is found that fulfills both of the following two conditions:

    1. The patterns in the clause head can be successfully matched against the given │ │ │ arguments.
    2. The guard sequence, if any, is true.

    If such a clause cannot be found, a function_clause runtime error occurs.

    If such a clause is found, the corresponding clause body is evaluated. That is, │ │ │ the expressions in the body are evaluated sequentially and the value of the last │ │ │ -expression is returned.

    Consider the function fact:

    -module(mod).
    │ │ │ --export([fact/1]).
    │ │ │ +expression is returned.

    Consider the function fact:

    -module(mod).
    │ │ │ +-export([fact/1]).
    │ │ │  
    │ │ │ -fact(N) when N > 0 ->
    │ │ │ -    N * fact(N - 1);
    │ │ │ -fact(0) ->
    │ │ │ +fact(N) when N > 0 ->
    │ │ │ +    N * fact(N - 1);
    │ │ │ +fact(0) ->
    │ │ │      1.

    Assume that you want to calculate the factorial for 1:

    1> mod:fact(1).

    Evaluation starts at the first clause. The pattern N is matched against │ │ │ argument 1. The matching succeeds and the guard (N > 0) is true, thus N is │ │ │ -bound to 1, and the corresponding body is evaluated:

    N * fact(N-1) => (N is bound to 1)
    │ │ │ -1 * fact(0)

    Now, fact(0) is called, and the function clauses are scanned │ │ │ +bound to 1, and the corresponding body is evaluated:

    N * fact(N-1) => (N is bound to 1)
    │ │ │ +1 * fact(0)

    Now, fact(0) is called, and the function clauses are scanned │ │ │ sequentially again. First, the pattern N is matched against 0. The │ │ │ matching succeeds, but the guard (N > 0) is false. Second, the │ │ │ pattern 0 is matched against the argument 0. The matching succeeds │ │ │ and the body is evaluated:

    1 * fact(0) =>
    │ │ │  1 * 1 =>
    │ │ │  1

    Evaluation has succeed and mod:fact(1) returns 1.

    If mod:fact/1 is called with a negative number as argument, no clause head │ │ │ matches. A function_clause runtime error occurs.

    │ │ │ @@ -173,17 +173,17 @@ │ │ │ │ │ │ Tail recursion │ │ │

    │ │ │

    If the last expression of a function body is a function call, a │ │ │ tail-recursive call is done. This is to ensure that no system │ │ │ resources, for example, call stack, are consumed. This means that an │ │ │ infinite loop using tail-recursive calls will not exhaust the call │ │ │ -stack and can (in principle) run forever.

    Example:

    loop(N) ->
    │ │ │ -    io:format("~w~n", [N]),
    │ │ │ -    loop(N+1).

    The earlier factorial example is a counter-example. It is not │ │ │ +stack and can (in principle) run forever.

    Example:

    loop(N) ->
    │ │ │ +    io:format("~w~n", [N]),
    │ │ │ +    loop(N+1).

    The earlier factorial example is a counter-example. It is not │ │ │ tail-recursive, since a multiplication is done on the result of the recursive │ │ │ call to fact(N-1).

    │ │ │ │ │ │ │ │ │ │ │ │ Built-In Functions (BIFs) │ │ │

    │ │ │ @@ -191,17 +191,17 @@ │ │ │ system. BIFs do things that are difficult or impossible to implement │ │ │ in Erlang. Most of the BIFs belong to module erlang, but there │ │ │ are also BIFs belonging to a few other modules, for example lists │ │ │ and ets.

    The most commonly used BIFs belonging to erlang are auto-imported. They do │ │ │ not need to be prefixed with the module name. Which BIFs that are auto-imported │ │ │ is specified in the erlang module in ERTS. For example, standard-type │ │ │ conversion BIFs like atom_to_list and BIFs allowed in guards can be called │ │ │ -without specifying the module name.

    Examples:

    1> tuple_size({a,b,c}).
    │ │ │ +without specifying the module name.

    Examples:

    1> tuple_size({a,b,c}).
    │ │ │  3
    │ │ │ -2> atom_to_list('Erlang').
    │ │ │ +2> atom_to_list('Erlang').
    │ │ │  "Erlang"
    │ │ │ │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ Process Creation │ │ │

    │ │ │ -

    A process is created by calling spawn():

    spawn(Module, Name, Args) -> pid()
    │ │ │ -  Module = Name = atom()
    │ │ │ -  Args = [Arg1,...,ArgN]
    │ │ │ -    ArgI = term()

    spawn() creates a new process and returns the pid.

    The new process starts executing in Module:Name(Arg1,...,ArgN) where the │ │ │ +

    A process is created by calling spawn():

    spawn(Module, Name, Args) -> pid()
    │ │ │ +  Module = Name = atom()
    │ │ │ +  Args = [Arg1,...,ArgN]
    │ │ │ +    ArgI = term()

    spawn() creates a new process and returns the pid.

    The new process starts executing in Module:Name(Arg1,...,ArgN) where the │ │ │ arguments are the elements of the (possible empty) Args argument list.

    There exist a number of different spawn BIFs:

    │ │ │ │ │ │ │ │ │ │ │ │ Registered Processes │ │ │

    │ │ │

    Besides addressing a process by using its pid, there are also BIFs for │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/ref_man_records.html │ │ │ @@ -123,17 +123,17 @@ │ │ │ │ │ │ │ │ │ Defining Records │ │ │ │ │ │

    A record definition consists of the name of the record, followed by the field │ │ │ names of the record. Record and field names must be atoms. Each field can be │ │ │ given an optional default value. If no default value is supplied, undefined is │ │ │ -used.

    -record(Name, {Field1 [= Expr1],
    │ │ │ +used.

    -record(Name, {Field1 [= Expr1],
    │ │ │                 ...
    │ │ │ -               FieldN [= ExprN]}).

    The default value for a field is an arbitrary expression, except that it must │ │ │ + FieldN [= ExprN]}).

    The default value for a field is an arbitrary expression, except that it must │ │ │ not use any variables.

    A record definition can be placed anywhere among the attributes and function │ │ │ declarations of a module, but the definition must come before any usage of the │ │ │ record.

    If a record is used in several modules, it is recommended that the record │ │ │ definition is placed in an include file.

    Change

    Starting from Erlang/OTP 26, records can be defined in the Erlang shell │ │ │ using the syntax described in this section. In earlier releases, it was │ │ │ necessary to use the shell built-in function rd/2.

    │ │ │ │ │ │ @@ -143,32 +143,32 @@ │ │ │

    │ │ │

    The following expression creates a new Name record where the value of each │ │ │ field FieldI is the value of evaluating the corresponding expression ExprI:

    #Name{Field1=Expr1, ..., FieldK=ExprK}

    The fields can be in any order, not necessarily the same order as in the record │ │ │ definition, and fields can be omitted. Omitted fields get their respective │ │ │ default value instead.

    If several fields are to be assigned the same value, the following construction │ │ │ can be used:

    #Name{Field1=Expr1, ..., FieldK=ExprK, _=ExprL}

    Omitted fields then get the value of evaluating ExprL instead of their default │ │ │ values. This feature is primarily intended to be used to create patterns for ETS │ │ │ -and Mnesia match functions.

    Example:

    -record(person, {name, phone, address}).
    │ │ │ +and Mnesia match functions.

    Example:

    -record(person, {name, phone, address}).
    │ │ │  
    │ │ │ -lookup(Name, Tab) ->
    │ │ │ -    ets:match_object(Tab, #person{name=Name, _='_'}).

    │ │ │ +lookup(Name, Tab) -> │ │ │ + ets:match_object(Tab, #person{name=Name, _='_'}).

    │ │ │ │ │ │ │ │ │ │ │ │ Accessing Record Fields │ │ │

    │ │ │
    Expr#Name.Field

    Returns the value of the specified field. Expr is to evaluate to a Name │ │ │ -record.

    Example:

    -record(person, {name, phone, address}).
    │ │ │ +record.

    Example:

    -record(person, {name, phone, address}).
    │ │ │  
    │ │ │ -get_person_name(Person) ->
    │ │ │ +get_person_name(Person) ->
    │ │ │      Person#person.name.

    The following expression returns the position of the specified field in the │ │ │ -tuple representation of the record:

    #Name.Field

    Example:

    -record(person, {name, phone, address}).
    │ │ │ +tuple representation of the record:

    #Name.Field

    Example:

    -record(person, {name, phone, address}).
    │ │ │  
    │ │ │ -lookup(Name, List) ->
    │ │ │ -    lists:keyfind(Name, #person.name, List).

    │ │ │ +lookup(Name, List) -> │ │ │ + lists:keyfind(Name, #person.name, List).

    │ │ │ │ │ │ │ │ │ │ │ │ Updating Records │ │ │

    │ │ │
    Expr#Name{Field1=Expr1, ..., FieldK=ExprK}

    Expr is to evaluate to a Name record. A copy of this record is returned, │ │ │ with the value of each specified field FieldI changed to the value of │ │ │ @@ -178,51 +178,51 @@ │ │ │ │ │ │ │ │ │ Records in Guards │ │ │ │ │ │

    Since record expressions are expanded to tuple expressions, creating │ │ │ records and accessing record fields are allowed in guards. However, │ │ │ all subexpressions (for initializing fields), must be valid guard │ │ │ -expressions as well.

    Examples:

    handle(Msg, State) when Msg =:= #msg{to=void, no=3} ->
    │ │ │ +expressions as well.

    Examples:

    handle(Msg, State) when Msg =:= #msg{to=void, no=3} ->
    │ │ │      ...
    │ │ │  
    │ │ │ -handle(Msg, State) when State#state.running =:= true ->
    │ │ │ -    ...

    There is also a type test BIF is_record(Term, RecordTag).

    Example:

    is_person(P) when is_record(P, person) ->
    │ │ │ +handle(Msg, State) when State#state.running =:= true ->
    │ │ │ +    ...

    There is also a type test BIF is_record(Term, RecordTag).

    Example:

    is_person(P) when is_record(P, person) ->
    │ │ │      true;
    │ │ │ -is_person(_P) ->
    │ │ │ +is_person(_P) ->
    │ │ │      false.

    │ │ │ │ │ │ │ │ │ │ │ │ Records in Patterns │ │ │

    │ │ │

    A pattern that matches a certain record is created in the same way as a record │ │ │ is created:

    #Name{Field1=Expr1, ..., FieldK=ExprK}

    In this case, one or more of Expr1 ... ExprK can be unbound variables.

    │ │ │ │ │ │ │ │ │ │ │ │ Nested Records │ │ │

    │ │ │ -

    Assume the following record definitions:

    -record(nrec0, {name = "nested0"}).
    │ │ │ --record(nrec1, {name = "nested1", nrec0=#nrec0{}}).
    │ │ │ --record(nrec2, {name = "nested2", nrec1=#nrec1{}}).
    │ │ │ +

    Assume the following record definitions:

    -record(nrec0, {name = "nested0"}).
    │ │ │ +-record(nrec1, {name = "nested1", nrec0=#nrec0{}}).
    │ │ │ +-record(nrec2, {name = "nested2", nrec1=#nrec1{}}).
    │ │ │  
    │ │ │ -N2 = #nrec2{},

    Accessing or updating nested records can be written without parentheses:

    "nested0" = N2#nrec2.nrec1#nrec1.nrec0#nrec0.name,
    │ │ │ +N2 = #nrec2{},

    Accessing or updating nested records can be written without parentheses:

    "nested0" = N2#nrec2.nrec1#nrec1.nrec0#nrec0.name,
    │ │ │      N0n = N2#nrec2.nrec1#nrec1.nrec0#nrec0{name = "nested0a"},

    which is equivalent to:

    "nested0" = ((N2#nrec2.nrec1)#nrec1.nrec0)#nrec0.name,
    │ │ │  N0n = ((N2#nrec2.nrec1)#nrec1.nrec0)#nrec0{name = "nested0a"},

    Change

    Before Erlang/OTP R14, parentheses were necessary when accessing or updating │ │ │ nested records.

    │ │ │ │ │ │ │ │ │ │ │ │ Internal Representation of Records │ │ │

    │ │ │

    Record expressions are translated to tuple expressions during compilation. A │ │ │ -record defined as:

    -record(Name, {Field1, ..., FieldN}).

    is internally represented by the tuple:

    {Name, Value1, ..., ValueN}

    Here each ValueI is the default value for FieldI.

    To each module using records, a pseudo function is added during compilation to │ │ │ -obtain information about records:

    record_info(fields, Record) -> [Field]
    │ │ │ -record_info(size, Record) -> Size

    Size is the size of the tuple representation, that is, one more than the │ │ │ +record defined as:

    -record(Name, {Field1, ..., FieldN}).

    is internally represented by the tuple:

    {Name, Value1, ..., ValueN}

    Here each ValueI is the default value for FieldI.

    To each module using records, a pseudo function is added during compilation to │ │ │ +obtain information about records:

    record_info(fields, Record) -> [Field]
    │ │ │ +record_info(size, Record) -> Size

    Size is the size of the tuple representation, that is, one more than the │ │ │ number of fields.

    │ │ │
    │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │

    gen_server, simple code replacement is not sufficient. │ │ │ Instead, it is necessary to:

    • Suspend the processes using the module (to avoid that they try to handle any │ │ │ requests before the code replacement is completed).
    • Ask them to transform the internal state format and switch to the new version │ │ │ of the module.
    • Remove the old version.
    • Resume the processes.

    This is called synchronized code replacement and for this the following │ │ │ -instructions are used:

    {update, Module, {advanced, Extra}}
    │ │ │ -{update, Module, supervisor}

    update with argument {advanced,Extra} is used when changing the internal │ │ │ +instructions are used:

    {update, Module, {advanced, Extra}}
    │ │ │ +{update, Module, supervisor}

    update with argument {advanced,Extra} is used when changing the internal │ │ │ state of a behaviour as described above. It causes behaviour processes to call │ │ │ the callback function code_change/3, passing the term Extra and some other │ │ │ information as arguments. See the manual pages for the respective behaviours and │ │ │ Appup Cookbook.

    update with argument supervisor is used when changing the start │ │ │ specification of a supervisor. See Appup Cookbook.

    When a module is to be updated, the release handler finds which processes that │ │ │ are using the module by traversing the supervision tree of each running │ │ │ -application and checking all the child specifications:

    {Id, StartFunc, Restart, Shutdown, Type, Modules}

    A process uses a module if the name is listed in Modules in the child │ │ │ +application and checking all the child specifications:

    {Id, StartFunc, Restart, Shutdown, Type, Modules}

    A process uses a module if the name is listed in Modules in the child │ │ │ specification for the process.

    If Modules=dynamic, which is the case for event managers, the event manager │ │ │ process informs the release handler about the list of currently installed event │ │ │ handlers (gen_event), and it is checked if the module name is in this list │ │ │ instead.

    The release handler suspends, asks for code change, and resumes processes by │ │ │ calling the functions sys:suspend/1,2, sys:change_code/4,5, and │ │ │ sys:resume/1,2, respectively.

    │ │ │ │ │ │ │ │ │ │ │ │ add_module and delete_module │ │ │

    │ │ │ -

    If a new module is introduced, the following instruction is used:

    {add_module, Module}

    This instruction loads module Module. When running Erlang in │ │ │ +

    If a new module is introduced, the following instruction is used:

    {add_module, Module}

    This instruction loads module Module. When running Erlang in │ │ │ embedded mode it is necessary to use this this instruction. It is not │ │ │ strictly required when running Erlang in interactive mode, since the │ │ │ -code server automatically searches for and loads unloaded modules.

    The opposite of add_module is delete_module, which unloads a module:

    {delete_module, Module}

    Any process, in any application, with Module as residence module, is │ │ │ +code server automatically searches for and loads unloaded modules.

    The opposite of add_module is delete_module, which unloads a module:

    {delete_module, Module}

    Any process, in any application, with Module as residence module, is │ │ │ killed when the instruction is evaluated. Therefore, the user must │ │ │ ensure that all such processes are terminated before deleting module │ │ │ Module to avoid a situation with failing supervisor restarts.

    │ │ │ │ │ │ │ │ │ │ │ │ Application Instructions │ │ │ @@ -341,60 +341,60 @@ │ │ │ .app file.
  • Each UpFromVsn is a previous version of the application to upgrade from.
  • Each DownToVsn is a previous version of the application to downgrade to.
  • Each Instructions is a list of release handling instructions.
  • UpFromVsn and DownToVsn can also be specified as regular expressions. For │ │ │ more information about the syntax and contents of the .appup file, see │ │ │ appup in SASL.

    Appup Cookbook includes examples of .appup files for │ │ │ typical upgrade/downgrade cases.

    Example: Consider the release ch_rel-1 from │ │ │ Releases. Assume you want to add a function │ │ │ available/0 to server ch3, which returns the number of available channels │ │ │ (when trying out the example, make the change in a copy of the original │ │ │ -directory, to ensure that the first version is still available):

    -module(ch3).
    │ │ │ --behaviour(gen_server).
    │ │ │ +directory, to ensure that the first version is still available):

    -module(ch3).
    │ │ │ +-behaviour(gen_server).
    │ │ │  
    │ │ │ --export([start_link/0]).
    │ │ │ --export([alloc/0, free/1]).
    │ │ │ --export([available/0]).
    │ │ │ --export([init/1, handle_call/3, handle_cast/2]).
    │ │ │ +-export([start_link/0]).
    │ │ │ +-export([alloc/0, free/1]).
    │ │ │ +-export([available/0]).
    │ │ │ +-export([init/1, handle_call/3, handle_cast/2]).
    │ │ │  
    │ │ │ -start_link() ->
    │ │ │ -    gen_server:start_link({local, ch3}, ch3, [], []).
    │ │ │ +start_link() ->
    │ │ │ +    gen_server:start_link({local, ch3}, ch3, [], []).
    │ │ │  
    │ │ │ -alloc() ->
    │ │ │ -    gen_server:call(ch3, alloc).
    │ │ │ +alloc() ->
    │ │ │ +    gen_server:call(ch3, alloc).
    │ │ │  
    │ │ │ -free(Ch) ->
    │ │ │ -    gen_server:cast(ch3, {free, Ch}).
    │ │ │ +free(Ch) ->
    │ │ │ +    gen_server:cast(ch3, {free, Ch}).
    │ │ │  
    │ │ │ -available() ->
    │ │ │ -    gen_server:call(ch3, available).
    │ │ │ +available() ->
    │ │ │ +    gen_server:call(ch3, available).
    │ │ │  
    │ │ │ -init(_Args) ->
    │ │ │ -    {ok, channels()}.
    │ │ │ +init(_Args) ->
    │ │ │ +    {ok, channels()}.
    │ │ │  
    │ │ │ -handle_call(alloc, _From, Chs) ->
    │ │ │ -    {Ch, Chs2} = alloc(Chs),
    │ │ │ -    {reply, Ch, Chs2};
    │ │ │ -handle_call(available, _From, Chs) ->
    │ │ │ -    N = available(Chs),
    │ │ │ -    {reply, N, Chs}.
    │ │ │ +handle_call(alloc, _From, Chs) ->
    │ │ │ +    {Ch, Chs2} = alloc(Chs),
    │ │ │ +    {reply, Ch, Chs2};
    │ │ │ +handle_call(available, _From, Chs) ->
    │ │ │ +    N = available(Chs),
    │ │ │ +    {reply, N, Chs}.
    │ │ │  
    │ │ │ -handle_cast({free, Ch}, Chs) ->
    │ │ │ -    Chs2 = free(Ch, Chs),
    │ │ │ -    {noreply, Chs2}.

    A new version of the ch_app.app file must now be created, where the version is │ │ │ -updated:

    {application, ch_app,
    │ │ │ - [{description, "Channel allocator"},
    │ │ │ -  {vsn, "2"},
    │ │ │ -  {modules, [ch_app, ch_sup, ch3]},
    │ │ │ -  {registered, [ch3]},
    │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ -  {mod, {ch_app,[]}}
    │ │ │ - ]}.

    To upgrade ch_app from "1" to "2" (and to downgrade from "2" to "1"), │ │ │ +handle_cast({free, Ch}, Chs) -> │ │ │ + Chs2 = free(Ch, Chs), │ │ │ + {noreply, Chs2}.

    A new version of the ch_app.app file must now be created, where the version is │ │ │ +updated:

    {application, ch_app,
    │ │ │ + [{description, "Channel allocator"},
    │ │ │ +  {vsn, "2"},
    │ │ │ +  {modules, [ch_app, ch_sup, ch3]},
    │ │ │ +  {registered, [ch3]},
    │ │ │ +  {applications, [kernel, stdlib, sasl]},
    │ │ │ +  {mod, {ch_app,[]}}
    │ │ │ + ]}.

    To upgrade ch_app from "1" to "2" (and to downgrade from "2" to "1"), │ │ │ you only need to load the new (old) version of the ch3 callback module. Create │ │ │ -the application upgrade file ch_app.appup in the ebin directory:

    {"2",
    │ │ │ - [{"1", [{load_module, ch3}]}],
    │ │ │ - [{"1", [{load_module, ch3}]}]
    │ │ │ -}.

    │ │ │ +the application upgrade file ch_app.appup in the ebin directory:

    {"2",
    │ │ │ + [{"1", [{load_module, ch3}]}],
    │ │ │ + [{"1", [{load_module, ch3}]}]
    │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ Release Upgrade File │ │ │

    │ │ │

    To define how to upgrade/downgrade between the new version and previous versions │ │ │ of a release, a release upgrade file, or in short .relup file, is to be │ │ │ @@ -405,22 +405,22 @@ │ │ │ are to be added and deleted, and which applications that must be upgraded and/or │ │ │ downgraded. The instructions for this are fetched from the .appup files and │ │ │ transformed into a single list of low-level instructions in the right order.

    If the relup file is relatively simple, it can be created manually. It is only │ │ │ to contain low-level instructions.

    For details about the syntax and contents of the release upgrade file, see │ │ │ relup in SASL.

    Example, continued from the previous section: You have a new version "2" of │ │ │ ch_app and an .appup file. A new version of the .rel file is also needed. │ │ │ This time the file is called ch_rel-2.rel and the release version string is │ │ │ -changed from "A" to "B":

    {release,
    │ │ │ - {"ch_rel", "B"},
    │ │ │ - {erts, "14.2.5"},
    │ │ │ - [{kernel, "9.2.4"},
    │ │ │ -  {stdlib, "5.2.3"},
    │ │ │ -  {sasl, "4.2.1"},
    │ │ │ -  {ch_app, "2"}]
    │ │ │ -}.

    Now the relup file can be generated:

    1> systools:make_relup("ch_rel-2", ["ch_rel-1"], ["ch_rel-1"]).
    │ │ │ +changed from "A" to "B":

    {release,
    │ │ │ + {"ch_rel", "B"},
    │ │ │ + {erts, "14.2.5"},
    │ │ │ + [{kernel, "9.2.4"},
    │ │ │ +  {stdlib, "5.2.3"},
    │ │ │ +  {sasl, "4.2.1"},
    │ │ │ +  {ch_app, "2"}]
    │ │ │ +}.

    Now the relup file can be generated:

    1> systools:make_relup("ch_rel-2", ["ch_rel-1"], ["ch_rel-1"]).
    │ │ │  ok

    This generates a relup file with instructions for how to upgrade from version │ │ │ "A" ("ch_rel-1") to version "B" ("ch_rel-2") and how to downgrade from version │ │ │ "B" to version "A".

    Both the old and new versions of the .app and .rel files must be in the code │ │ │ path, as well as the .appup and (new) .beam files. The code path can be │ │ │ extended by using the option path:

    1> systools:make_relup("ch_rel-2", ["ch_rel-1"], ["ch_rel-1"],
    │ │ │  [{path,["../ch_rel-1",
    │ │ │  "../ch_rel-1/lib/ch_app-1/ebin"]}]).
    │ │ │ @@ -433,25 +433,25 @@
    │ │ │  

    When you have made a new version of a release, a release package can be created │ │ │ with this new version and transferred to the target environment.

    To install the new version of the release in runtime, the release │ │ │ handler is used. This is a process belonging to the SASL application, │ │ │ which handles unpacking, installation, and removal of release │ │ │ packages. The release_handler module communicates with this process.

    Assuming there is an operational target system with installation root directory │ │ │ $ROOT, the release package with the new version of the release is to be copied │ │ │ to $ROOT/releases.

    First, unpack the release package. The files are then extracted from the │ │ │ -package:

    release_handler:unpack_release(ReleaseName) => {ok, Vsn}
    • ReleaseName is the name of the release package except the .tar.gz │ │ │ +package:

      release_handler:unpack_release(ReleaseName) => {ok, Vsn}
      • ReleaseName is the name of the release package except the .tar.gz │ │ │ extension.
      • Vsn is the version of the unpacked release, as defined in its .rel file.

      A directory $ROOT/lib/releases/Vsn is created, where the .rel file, the boot │ │ │ script start.boot, the system configuration file sys.config, and relup are │ │ │ placed. For applications with new version numbers, the application directories │ │ │ are placed under $ROOT/lib. Unchanged applications are not affected.

      An unpacked release can be installed. The release handler then evaluates the │ │ │ -instructions in relup, step by step:

      release_handler:install_release(Vsn) => {ok, FromVsn, []}

      If an error occurs during the installation, the system is rebooted using the old │ │ │ +instructions in relup, step by step:

      release_handler:install_release(Vsn) => {ok, FromVsn, []}

      If an error occurs during the installation, the system is rebooted using the old │ │ │ version of the release. If installation succeeds, the system is afterwards using │ │ │ the new version of the release, but if anything happens and the system is │ │ │ rebooted, it starts using the previous version again.

      To be made the default version, the newly installed release must be made │ │ │ permanent, which means the previous version becomes old:

      release_handler:make_permanent(Vsn) => ok

      The system keeps information about which versions are old and permanent in the │ │ │ -files $ROOT/releases/RELEASES and $ROOT/releases/start_erl.data.

      To downgrade from Vsn to FromVsn, install_release must be called again:

      release_handler:install_release(FromVsn) => {ok, Vsn, []}

      An installed, but not permanent, release can be removed. Information about the │ │ │ +files $ROOT/releases/RELEASES and $ROOT/releases/start_erl.data.

      To downgrade from Vsn to FromVsn, install_release must be called again:

      release_handler:install_release(FromVsn) => {ok, Vsn, []}

      An installed, but not permanent, release can be removed. Information about the │ │ │ release is then deleted from $ROOT/releases/RELEASES and the release-specific │ │ │ code, that is, the new application directories and the $ROOT/releases/Vsn │ │ │ directory, are removed.

      release_handler:remove_release(Vsn) => ok

      │ │ │ │ │ │ │ │ │ │ │ │ Example (continued from the previous sections) │ │ │ @@ -462,17 +462,17 @@ │ │ │ is needed, the file is to contain the empty list:

      [].

      Step 2) Start the system as a simple target system. In reality, it is to be │ │ │ started as an embedded system. However, using erl with the correct boot script │ │ │ and config file is enough for illustration purposes:

      % cd $ROOT
      │ │ │  % bin/erl -boot $ROOT/releases/A/start -config $ROOT/releases/A/sys
      │ │ │  ...

      $ROOT is the installation directory of the target system.

      Step 3) In another Erlang shell, generate start scripts and create a release │ │ │ package for the new version "B". Remember to include (a possible updated) │ │ │ sys.config and the relup file. For more information, see │ │ │ -Release Upgrade File.

      1> systools:make_script("ch_rel-2").
      │ │ │ +Release Upgrade File.

      1> systools:make_script("ch_rel-2").
      │ │ │  ok
      │ │ │ -2> systools:make_tar("ch_rel-2").
      │ │ │ +2> systools:make_tar("ch_rel-2").
      │ │ │  ok

      The new release package now also contains version "2" of ch_app and the │ │ │ relup file:

      % tar tf ch_rel-2.tar
      │ │ │  lib/kernel-9.2.4/ebin/kernel.app
      │ │ │  lib/kernel-9.2.4/ebin/application.beam
      │ │ │  ...
      │ │ │  lib/stdlib-5.2.3/ebin/stdlib.app
      │ │ │  lib/stdlib-5.2.3/ebin/argparse.beam
      │ │ │ @@ -485,31 +485,31 @@
      │ │ │  lib/ch_app-2/ebin/ch_sup.beam
      │ │ │  lib/ch_app-2/ebin/ch3.beam
      │ │ │  releases/B/start.boot
      │ │ │  releases/B/relup
      │ │ │  releases/B/sys.config
      │ │ │  releases/B/ch_rel-2.rel
      │ │ │  releases/ch_rel-2.rel

      Step 4) Copy the release package ch_rel-2.tar.gz to the $ROOT/releases │ │ │ -directory.

      Step 5) In the running target system, unpack the release package:

      1> release_handler:unpack_release("ch_rel-2").
      │ │ │ -{ok,"B"}

      The new application version ch_app-2 is installed under $ROOT/lib next to │ │ │ +directory.

      Step 5) In the running target system, unpack the release package:

      1> release_handler:unpack_release("ch_rel-2").
      │ │ │ +{ok,"B"}

      The new application version ch_app-2 is installed under $ROOT/lib next to │ │ │ ch_app-1. The kernel, stdlib, and sasl directories are not affected, as │ │ │ they have not changed.

      Under $ROOT/releases, a new directory B is created, containing │ │ │ -ch_rel-2.rel, start.boot, sys.config, and relup.

      Step 6) Check if the function ch3:available/0 is available:

      2> ch3:available().
      │ │ │ +ch_rel-2.rel, start.boot, sys.config, and relup.

      Step 6) Check if the function ch3:available/0 is available:

      2> ch3:available().
      │ │ │  ** exception error: undefined function ch3:available/0

      Step 7) Install the new release. The instructions in $ROOT/releases/B/relup │ │ │ are executed one by one, resulting in the new version of ch3 being loaded. The │ │ │ -function ch3:available/0 is now available:

      3> release_handler:install_release("B").
      │ │ │ -{ok,"A",[]}
      │ │ │ -4> ch3:available().
      │ │ │ +function ch3:available/0 is now available:

      3> release_handler:install_release("B").
      │ │ │ +{ok,"A",[]}
      │ │ │ +4> ch3:available().
      │ │ │  3
      │ │ │ -5> code:which(ch3).
      │ │ │ +5> code:which(ch3).
      │ │ │  ".../lib/ch_app-2/ebin/ch3.beam"
      │ │ │ -6> code:which(ch_sup).
      │ │ │ +6> code:which(ch_sup).
      │ │ │  ".../lib/ch_app-1/ebin/ch_sup.beam"

      Processes in ch_app for which code have not been updated, for example, the │ │ │ supervisor, are still evaluating code from ch_app-1.

      Step 8) If the target system is now rebooted, it uses version "A" again. The │ │ │ -"B" version must be made permanent, to be used when the system is rebooted.

      7> release_handler:make_permanent("B").
      │ │ │ +"B" version must be made permanent, to be used when the system is rebooted.

      7> release_handler:make_permanent("B").
      │ │ │  ok

      │ │ │ │ │ │ │ │ │ │ │ │ Updating Application Specifications │ │ │

      │ │ │

      When a new version of a release is installed, the application specifications are │ │ │ @@ -518,15 +518,15 @@ │ │ │ boot script is generated from the same .rel file as is used to build the │ │ │ release package itself.

      Specifically, the application configuration parameters are automatically updated │ │ │ according to (in increasing priority order):

      • The data in the boot script, fetched from the new application resource file │ │ │ App.app
      • The new sys.config
      • Command-line arguments -App Par Val

      This means that parameter values set in the other system configuration files and │ │ │ values set using application:set_env/3 are disregarded.

      When an installed release is made permanent, the system process init is set to │ │ │ point out the new sys.config.

      After the installation, the application controller compares the old and new │ │ │ configuration parameters for all running applications and call the callback │ │ │ -function:

      Module:config_change(Changed, New, Removed)
      • Module is the application callback module as defined by the mod key in the │ │ │ +function:

        Module:config_change(Changed, New, Removed)
        • Module is the application callback module as defined by the mod key in the │ │ │ .app file.
        • Changed and New are lists of {Par,Val} for all changed and added │ │ │ configuration parameters, respectively.
        • Removed is a list of all parameters Par that have been removed.

        The function is optional and can be omitted when implementing an application │ │ │ callback module.

        │ │ │

    │ │ │ │ │ │
    │ │ │
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/release_structure.html │ │ │ @@ -136,37 +136,37 @@ │ │ │ │ │ │ │ │ │ │ │ │ Release Resource File │ │ │ │ │ │

    To define a release, create a release resource file, or in short a .rel │ │ │ file. In the file, specify the name and version of the release, which ERTS │ │ │ -version it is based on, and which applications it consists of:

    {release, {Name,Vsn}, {erts, EVsn},
    │ │ │ - [{Application1, AppVsn1},
    │ │ │ +version it is based on, and which applications it consists of:

    {release, {Name,Vsn}, {erts, EVsn},
    │ │ │ + [{Application1, AppVsn1},
    │ │ │     ...
    │ │ │ -  {ApplicationN, AppVsnN}]}.

    Name, Vsn, EVsn, and AppVsn are strings.

    The file must be named Rel.rel, where Rel is a unique name.

    Each Application (atom) and AppVsn is the name and version of an application │ │ │ + {ApplicationN, AppVsnN}]}.

    Name, Vsn, EVsn, and AppVsn are strings.

    The file must be named Rel.rel, where Rel is a unique name.

    Each Application (atom) and AppVsn is the name and version of an application │ │ │ included in the release. The minimal release based on Erlang/OTP consists of the │ │ │ Kernel and STDLIB applications, so these applications must be included in the │ │ │ list.

    If the release is to be upgraded, it must also include the SASL application.

    Here is an example showing the .app file for a release of ch_app from │ │ │ -the Applications section:

    {application, ch_app,
    │ │ │ - [{description, "Channel allocator"},
    │ │ │ -  {vsn, "1"},
    │ │ │ -  {modules, [ch_app, ch_sup, ch3]},
    │ │ │ -  {registered, [ch3]},
    │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ -  {mod, {ch_app,[]}}
    │ │ │ - ]}.

    The .rel file must also contain kernel, stdlib, and sasl, as these │ │ │ -applications are required by ch_app. The file is called ch_rel-1.rel:

    {release,
    │ │ │ - {"ch_rel", "A"},
    │ │ │ - {erts, "14.2.5"},
    │ │ │ - [{kernel, "9.2.4"},
    │ │ │ -  {stdlib, "5.2.3"},
    │ │ │ -  {sasl, "4.2.1"},
    │ │ │ -  {ch_app, "1"}]
    │ │ │ -}.

    │ │ │ +the Applications section:

    {application, ch_app,
    │ │ │ + [{description, "Channel allocator"},
    │ │ │ +  {vsn, "1"},
    │ │ │ +  {modules, [ch_app, ch_sup, ch3]},
    │ │ │ +  {registered, [ch3]},
    │ │ │ +  {applications, [kernel, stdlib, sasl]},
    │ │ │ +  {mod, {ch_app,[]}}
    │ │ │ + ]}.

    The .rel file must also contain kernel, stdlib, and sasl, as these │ │ │ +applications are required by ch_app. The file is called ch_rel-1.rel:

    {release,
    │ │ │ + {"ch_rel", "A"},
    │ │ │ + {erts, "14.2.5"},
    │ │ │ + [{kernel, "9.2.4"},
    │ │ │ +  {stdlib, "5.2.3"},
    │ │ │ +  {sasl, "4.2.1"},
    │ │ │ +  {ch_app, "1"}]
    │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ Generating Boot Scripts │ │ │

    │ │ │

    systools in the SASL application includes tools to build and check │ │ │ releases. The functions read the .rel and .app files and perform │ │ │ @@ -190,17 +190,17 @@ │ │ │ │ │ │ │ │ │ │ │ │ Creating a Release Package │ │ │ │ │ │

    The systools:make_tar/1,2 function takes a │ │ │ .rel file as input and creates a zipped tar file with the code for │ │ │ -the specified applications, a release package:

    1> systools:make_script("ch_rel-1").
    │ │ │ +the specified applications, a release package:

    1> systools:make_script("ch_rel-1").
    │ │ │  ok
    │ │ │ -2> systools:make_tar("ch_rel-1").
    │ │ │ +2> systools:make_tar("ch_rel-1").
    │ │ │  ok

    The release package by default contains:

    • The .app files
    • The .rel file
    • The object code for all applications, structured according to the │ │ │ application directory structure
    • The binary boot script renamed to start.boot
    % tar tf ch_rel-1.tar
    │ │ │  lib/kernel-9.2.4/ebin/kernel.app
    │ │ │  lib/kernel-9.2.4/ebin/application.beam
    │ │ │  ...
    │ │ │  lib/stdlib-5.2.3/ebin/stdlib.app
    │ │ │  lib/stdlib-5.2.3/ebin/argparse.beam
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/robustness.html
    │ │ │ @@ -128,68 +128,68 @@
    │ │ │  
    │ │ │  

    Before improving the messenger program, let us look at some general principles, │ │ │ using the ping pong program as an example. Recall that when "ping" finishes, it │ │ │ tells "pong" that it has done so by sending the atom finished as a message to │ │ │ "pong" so that "pong" can also finish. Another way to let "pong" finish is to │ │ │ make "pong" exit if it does not receive a message from ping within a certain │ │ │ time. This can be done by adding a time-out to pong as shown in the │ │ │ -following example:

    -module(tut19).
    │ │ │ +following example:

    -module(tut19).
    │ │ │  
    │ │ │ --export([start_ping/1, start_pong/0,  ping/2, pong/0]).
    │ │ │ +-export([start_ping/1, start_pong/0,  ping/2, pong/0]).
    │ │ │  
    │ │ │ -ping(0, Pong_Node) ->
    │ │ │ -    io:format("ping finished~n", []);
    │ │ │ +ping(0, Pong_Node) ->
    │ │ │ +    io:format("ping finished~n", []);
    │ │ │  
    │ │ │ -ping(N, Pong_Node) ->
    │ │ │ -    {pong, Pong_Node} ! {ping, self()},
    │ │ │ +ping(N, Pong_Node) ->
    │ │ │ +    {pong, Pong_Node} ! {ping, self()},
    │ │ │      receive
    │ │ │          pong ->
    │ │ │ -            io:format("Ping received pong~n", [])
    │ │ │ +            io:format("Ping received pong~n", [])
    │ │ │      end,
    │ │ │ -    ping(N - 1, Pong_Node).
    │ │ │ +    ping(N - 1, Pong_Node).
    │ │ │  
    │ │ │ -pong() ->
    │ │ │ +pong() ->
    │ │ │      receive
    │ │ │ -        {ping, Ping_PID} ->
    │ │ │ -            io:format("Pong received ping~n", []),
    │ │ │ +        {ping, Ping_PID} ->
    │ │ │ +            io:format("Pong received ping~n", []),
    │ │ │              Ping_PID ! pong,
    │ │ │ -            pong()
    │ │ │ +            pong()
    │ │ │      after 5000 ->
    │ │ │ -            io:format("Pong timed out~n", [])
    │ │ │ +            io:format("Pong timed out~n", [])
    │ │ │      end.
    │ │ │  
    │ │ │ -start_pong() ->
    │ │ │ -    register(pong, spawn(tut19, pong, [])).
    │ │ │ +start_pong() ->
    │ │ │ +    register(pong, spawn(tut19, pong, [])).
    │ │ │  
    │ │ │ -start_ping(Pong_Node) ->
    │ │ │ -    spawn(tut19, ping, [3, Pong_Node]).

    After this is compiled and the file tut19.beam is copied to the necessary │ │ │ +start_ping(Pong_Node) -> │ │ │ + spawn(tut19, ping, [3, Pong_Node]).

    After this is compiled and the file tut19.beam is copied to the necessary │ │ │ directories, the following is seen on (pong@kosken):

    (pong@kosken)1> tut19:start_pong().
    │ │ │  true
    │ │ │  Pong received ping
    │ │ │  Pong received ping
    │ │ │  Pong received ping
    │ │ │  Pong timed out

    And the following is seen on (ping@gollum):

    (ping@gollum)1> tut19:start_ping(pong@kosken).
    │ │ │  <0.36.0>
    │ │ │  Ping received pong
    │ │ │  Ping received pong
    │ │ │  Ping received pong
    │ │ │ -ping finished

    The time-out is set in:

    pong() ->
    │ │ │ +ping finished

    The time-out is set in:

    pong() ->
    │ │ │      receive
    │ │ │ -        {ping, Ping_PID} ->
    │ │ │ -            io:format("Pong received ping~n", []),
    │ │ │ +        {ping, Ping_PID} ->
    │ │ │ +            io:format("Pong received ping~n", []),
    │ │ │              Ping_PID ! pong,
    │ │ │ -            pong()
    │ │ │ +            pong()
    │ │ │      after 5000 ->
    │ │ │ -            io:format("Pong timed out~n", [])
    │ │ │ +            io:format("Pong timed out~n", [])
    │ │ │      end.

    The time-out (after 5000) is started when receive is entered. The time-out │ │ │ is canceled if {ping,Ping_PID} is received. If {ping,Ping_PID} is not │ │ │ received, the actions following the time-out are done after 5000 milliseconds. │ │ │ after must be last in the receive, that is, preceded by all other message │ │ │ reception specifications in the receive. It is also possible to call a │ │ │ -function that returned an integer for the time-out:

    after pong_timeout() ->

    In general, there are better ways than using time-outs to supervise parts of a │ │ │ +function that returned an integer for the time-out:

    after pong_timeout() ->

    In general, there are better ways than using time-outs to supervise parts of a │ │ │ distributed Erlang system. Time-outs are usually appropriate to supervise │ │ │ external events, for example, if you have expected a message from some external │ │ │ system within a specified time. For example, a time-out can be used to log a │ │ │ user out of the messenger system if they have not accessed it for, say, ten │ │ │ minutes.

    │ │ │ │ │ │ │ │ │ @@ -209,96 +209,96 @@ │ │ │ something called a signal to all the processes it has links to.

    The signal carries information about the pid it was sent from and the exit │ │ │ reason.

    The default behaviour of a process that receives a normal exit is to ignore the │ │ │ signal.

    The default behaviour in the two other cases (that is, abnormal exit) above is │ │ │ to:

    • Bypass all messages to the receiving process.
    • Kill the receiving process.
    • Propagate the same error signal to the links of the killed process.

    In this way you can connect all processes in a transaction together using links. │ │ │ If one of the processes exits abnormally, all the processes in the transaction │ │ │ are killed. As it is often wanted to create a process and link to it at the same │ │ │ time, there is a special BIF, spawn_link that does the │ │ │ -same as spawn, but also creates a link to the spawned process.

    Now an example of the ping pong example using links to terminate "pong":

    -module(tut20).
    │ │ │ +same as spawn, but also creates a link to the spawned process.

    Now an example of the ping pong example using links to terminate "pong":

    -module(tut20).
    │ │ │  
    │ │ │ --export([start/1,  ping/2, pong/0]).
    │ │ │ +-export([start/1,  ping/2, pong/0]).
    │ │ │  
    │ │ │ -ping(N, Pong_Pid) ->
    │ │ │ -    link(Pong_Pid),
    │ │ │ -    ping1(N, Pong_Pid).
    │ │ │ +ping(N, Pong_Pid) ->
    │ │ │ +    link(Pong_Pid),
    │ │ │ +    ping1(N, Pong_Pid).
    │ │ │  
    │ │ │ -ping1(0, _) ->
    │ │ │ -    exit(ping);
    │ │ │ +ping1(0, _) ->
    │ │ │ +    exit(ping);
    │ │ │  
    │ │ │ -ping1(N, Pong_Pid) ->
    │ │ │ -    Pong_Pid ! {ping, self()},
    │ │ │ +ping1(N, Pong_Pid) ->
    │ │ │ +    Pong_Pid ! {ping, self()},
    │ │ │      receive
    │ │ │          pong ->
    │ │ │ -            io:format("Ping received pong~n", [])
    │ │ │ +            io:format("Ping received pong~n", [])
    │ │ │      end,
    │ │ │ -    ping1(N - 1, Pong_Pid).
    │ │ │ +    ping1(N - 1, Pong_Pid).
    │ │ │  
    │ │ │ -pong() ->
    │ │ │ +pong() ->
    │ │ │      receive
    │ │ │ -        {ping, Ping_PID} ->
    │ │ │ -            io:format("Pong received ping~n", []),
    │ │ │ +        {ping, Ping_PID} ->
    │ │ │ +            io:format("Pong received ping~n", []),
    │ │ │              Ping_PID ! pong,
    │ │ │ -            pong()
    │ │ │ +            pong()
    │ │ │      end.
    │ │ │  
    │ │ │ -start(Ping_Node) ->
    │ │ │ -    PongPID = spawn(tut20, pong, []),
    │ │ │ -    spawn(Ping_Node, tut20, ping, [3, PongPID]).
    (s1@bill)3> tut20:start(s2@kosken).
    │ │ │ +start(Ping_Node) ->
    │ │ │ +    PongPID = spawn(tut20, pong, []),
    │ │ │ +    spawn(Ping_Node, tut20, ping, [3, PongPID]).
    (s1@bill)3> tut20:start(s2@kosken).
    │ │ │  Pong received ping
    │ │ │  <3820.41.0>
    │ │ │  Ping received pong
    │ │ │  Pong received ping
    │ │ │  Ping received pong
    │ │ │  Pong received ping
    │ │ │  Ping received pong

    This is a slight modification of the ping pong program where both processes are │ │ │ spawned from the same start/1 function, and the "ping" process can be spawned │ │ │ on a separate node. Notice the use of the link BIF. "Ping" calls │ │ │ exit(ping) when it finishes and this causes an exit signal to be │ │ │ sent to "pong", which also terminates.

    It is possible to modify the default behaviour of a process so that it does not │ │ │ get killed when it receives abnormal exit signals. Instead, all signals are │ │ │ turned into normal messages on the format {'EXIT',FromPID,Reason} and added to │ │ │ -the end of the receiving process' message queue. This behaviour is set by:

    process_flag(trap_exit, true)

    There are several other process flags, see erlang(3). │ │ │ +the end of the receiving process' message queue. This behaviour is set by:

    process_flag(trap_exit, true)

    There are several other process flags, see erlang(3). │ │ │ Changing the default behaviour of a process in this way is usually not done in │ │ │ standard user programs, but is left to the supervisory programs in OTP. However, │ │ │ -the ping pong program is modified to illustrate exit trapping.

    -module(tut21).
    │ │ │ +the ping pong program is modified to illustrate exit trapping.

    -module(tut21).
    │ │ │  
    │ │ │ --export([start/1,  ping/2, pong/0]).
    │ │ │ +-export([start/1,  ping/2, pong/0]).
    │ │ │  
    │ │ │ -ping(N, Pong_Pid) ->
    │ │ │ -    link(Pong_Pid),
    │ │ │ -    ping1(N, Pong_Pid).
    │ │ │ +ping(N, Pong_Pid) ->
    │ │ │ +    link(Pong_Pid),
    │ │ │ +    ping1(N, Pong_Pid).
    │ │ │  
    │ │ │ -ping1(0, _) ->
    │ │ │ -    exit(ping);
    │ │ │ +ping1(0, _) ->
    │ │ │ +    exit(ping);
    │ │ │  
    │ │ │ -ping1(N, Pong_Pid) ->
    │ │ │ -    Pong_Pid ! {ping, self()},
    │ │ │ +ping1(N, Pong_Pid) ->
    │ │ │ +    Pong_Pid ! {ping, self()},
    │ │ │      receive
    │ │ │          pong ->
    │ │ │ -            io:format("Ping received pong~n", [])
    │ │ │ +            io:format("Ping received pong~n", [])
    │ │ │      end,
    │ │ │ -    ping1(N - 1, Pong_Pid).
    │ │ │ +    ping1(N - 1, Pong_Pid).
    │ │ │  
    │ │ │ -pong() ->
    │ │ │ -    process_flag(trap_exit, true),
    │ │ │ -    pong1().
    │ │ │ +pong() ->
    │ │ │ +    process_flag(trap_exit, true),
    │ │ │ +    pong1().
    │ │ │  
    │ │ │ -pong1() ->
    │ │ │ +pong1() ->
    │ │ │      receive
    │ │ │ -        {ping, Ping_PID} ->
    │ │ │ -            io:format("Pong received ping~n", []),
    │ │ │ +        {ping, Ping_PID} ->
    │ │ │ +            io:format("Pong received ping~n", []),
    │ │ │              Ping_PID ! pong,
    │ │ │ -            pong1();
    │ │ │ -        {'EXIT', From, Reason} ->
    │ │ │ -            io:format("pong exiting, got ~p~n", [{'EXIT', From, Reason}])
    │ │ │ +            pong1();
    │ │ │ +        {'EXIT', From, Reason} ->
    │ │ │ +            io:format("pong exiting, got ~p~n", [{'EXIT', From, Reason}])
    │ │ │      end.
    │ │ │  
    │ │ │ -start(Ping_Node) ->
    │ │ │ -    PongPID = spawn(tut21, pong, []),
    │ │ │ -    spawn(Ping_Node, tut21, ping, [3, PongPID]).
    (s1@bill)1> tut21:start(s2@gollum).
    │ │ │ +start(Ping_Node) ->
    │ │ │ +    PongPID = spawn(tut21, pong, []),
    │ │ │ +    spawn(Ping_Node, tut21, ping, [3, PongPID]).
    (s1@bill)1> tut21:start(s2@gollum).
    │ │ │  <3820.39.0>
    │ │ │  Pong received ping
    │ │ │  Ping received pong
    │ │ │  Pong received ping
    │ │ │  Ping received pong
    │ │ │  Pong received ping
    │ │ │  Ping received pong
    │ │ │ @@ -351,135 +351,135 @@
    │ │ │  %%% Started: messenger:client(Server_Node, Name)
    │ │ │  %%% To client: logoff
    │ │ │  %%% To client: {message_to, ToName, Message}
    │ │ │  %%%
    │ │ │  %%% Configuration: change the server_node() function to return the
    │ │ │  %%% name of the node where the messenger server runs
    │ │ │  
    │ │ │ --module(messenger).
    │ │ │ --export([start_server/0, server/0,
    │ │ │ -         logon/1, logoff/0, message/2, client/2]).
    │ │ │ +-module(messenger).
    │ │ │ +-export([start_server/0, server/0,
    │ │ │ +         logon/1, logoff/0, message/2, client/2]).
    │ │ │  
    │ │ │  %%% Change the function below to return the name of the node where the
    │ │ │  %%% messenger server runs
    │ │ │ -server_node() ->
    │ │ │ +server_node() ->
    │ │ │      messenger@super.
    │ │ │  
    │ │ │  %%% This is the server process for the "messenger"
    │ │ │  %%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
    │ │ │ -server() ->
    │ │ │ -    process_flag(trap_exit, true),
    │ │ │ -    server([]).
    │ │ │ +server() ->
    │ │ │ +    process_flag(trap_exit, true),
    │ │ │ +    server([]).
    │ │ │  
    │ │ │ -server(User_List) ->
    │ │ │ +server(User_List) ->
    │ │ │      receive
    │ │ │ -        {From, logon, Name} ->
    │ │ │ -            New_User_List = server_logon(From, Name, User_List),
    │ │ │ -            server(New_User_List);
    │ │ │ -        {'EXIT', From, _} ->
    │ │ │ -            New_User_List = server_logoff(From, User_List),
    │ │ │ -            server(New_User_List);
    │ │ │ -        {From, message_to, To, Message} ->
    │ │ │ -            server_transfer(From, To, Message, User_List),
    │ │ │ -            io:format("list is now: ~p~n", [User_List]),
    │ │ │ -            server(User_List)
    │ │ │ +        {From, logon, Name} ->
    │ │ │ +            New_User_List = server_logon(From, Name, User_List),
    │ │ │ +            server(New_User_List);
    │ │ │ +        {'EXIT', From, _} ->
    │ │ │ +            New_User_List = server_logoff(From, User_List),
    │ │ │ +            server(New_User_List);
    │ │ │ +        {From, message_to, To, Message} ->
    │ │ │ +            server_transfer(From, To, Message, User_List),
    │ │ │ +            io:format("list is now: ~p~n", [User_List]),
    │ │ │ +            server(User_List)
    │ │ │      end.
    │ │ │  
    │ │ │  %%% Start the server
    │ │ │ -start_server() ->
    │ │ │ -    register(messenger, spawn(messenger, server, [])).
    │ │ │ +start_server() ->
    │ │ │ +    register(messenger, spawn(messenger, server, [])).
    │ │ │  
    │ │ │  %%% Server adds a new user to the user list
    │ │ │ -server_logon(From, Name, User_List) ->
    │ │ │ +server_logon(From, Name, User_List) ->
    │ │ │      %% check if logged on anywhere else
    │ │ │ -    case lists:keymember(Name, 2, User_List) of
    │ │ │ +    case lists:keymember(Name, 2, User_List) of
    │ │ │          true ->
    │ │ │ -            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
    │ │ │ +            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
    │ │ │              User_List;
    │ │ │          false ->
    │ │ │ -            From ! {messenger, logged_on},
    │ │ │ -            link(From),
    │ │ │ -            [{From, Name} | User_List]        %add user to the list
    │ │ │ +            From ! {messenger, logged_on},
    │ │ │ +            link(From),
    │ │ │ +            [{From, Name} | User_List]        %add user to the list
    │ │ │      end.
    │ │ │  
    │ │ │  %%% Server deletes a user from the user list
    │ │ │ -server_logoff(From, User_List) ->
    │ │ │ -    lists:keydelete(From, 1, User_List).
    │ │ │ +server_logoff(From, User_List) ->
    │ │ │ +    lists:keydelete(From, 1, User_List).
    │ │ │  
    │ │ │  
    │ │ │  %%% Server transfers a message between user
    │ │ │ -server_transfer(From, To, Message, User_List) ->
    │ │ │ +server_transfer(From, To, Message, User_List) ->
    │ │ │      %% check that the user is logged on and who he is
    │ │ │ -    case lists:keysearch(From, 1, User_List) of
    │ │ │ +    case lists:keysearch(From, 1, User_List) of
    │ │ │          false ->
    │ │ │ -            From ! {messenger, stop, you_are_not_logged_on};
    │ │ │ -        {value, {_, Name}} ->
    │ │ │ -            server_transfer(From, Name, To, Message, User_List)
    │ │ │ +            From ! {messenger, stop, you_are_not_logged_on};
    │ │ │ +        {value, {_, Name}} ->
    │ │ │ +            server_transfer(From, Name, To, Message, User_List)
    │ │ │      end.
    │ │ │  
    │ │ │  %%% If the user exists, send the message
    │ │ │ -server_transfer(From, Name, To, Message, User_List) ->
    │ │ │ +server_transfer(From, Name, To, Message, User_List) ->
    │ │ │      %% Find the receiver and send the message
    │ │ │ -    case lists:keysearch(To, 2, User_List) of
    │ │ │ +    case lists:keysearch(To, 2, User_List) of
    │ │ │          false ->
    │ │ │ -            From ! {messenger, receiver_not_found};
    │ │ │ -        {value, {ToPid, To}} ->
    │ │ │ -            ToPid ! {message_from, Name, Message},
    │ │ │ -            From ! {messenger, sent}
    │ │ │ +            From ! {messenger, receiver_not_found};
    │ │ │ +        {value, {ToPid, To}} ->
    │ │ │ +            ToPid ! {message_from, Name, Message},
    │ │ │ +            From ! {messenger, sent}
    │ │ │      end.
    │ │ │  
    │ │ │  %%% User Commands
    │ │ │ -logon(Name) ->
    │ │ │ -    case whereis(mess_client) of
    │ │ │ +logon(Name) ->
    │ │ │ +    case whereis(mess_client) of
    │ │ │          undefined ->
    │ │ │ -            register(mess_client,
    │ │ │ -                     spawn(messenger, client, [server_node(), Name]));
    │ │ │ +            register(mess_client,
    │ │ │ +                     spawn(messenger, client, [server_node(), Name]));
    │ │ │          _ -> already_logged_on
    │ │ │      end.
    │ │ │  
    │ │ │ -logoff() ->
    │ │ │ +logoff() ->
    │ │ │      mess_client ! logoff.
    │ │ │  
    │ │ │ -message(ToName, Message) ->
    │ │ │ -    case whereis(mess_client) of % Test if the client is running
    │ │ │ +message(ToName, Message) ->
    │ │ │ +    case whereis(mess_client) of % Test if the client is running
    │ │ │          undefined ->
    │ │ │              not_logged_on;
    │ │ │ -        _ -> mess_client ! {message_to, ToName, Message},
    │ │ │ +        _ -> mess_client ! {message_to, ToName, Message},
    │ │ │               ok
    │ │ │  end.
    │ │ │  
    │ │ │  %%% The client process which runs on each user node
    │ │ │ -client(Server_Node, Name) ->
    │ │ │ -    {messenger, Server_Node} ! {self(), logon, Name},
    │ │ │ -    await_result(),
    │ │ │ -    client(Server_Node).
    │ │ │ +client(Server_Node, Name) ->
    │ │ │ +    {messenger, Server_Node} ! {self(), logon, Name},
    │ │ │ +    await_result(),
    │ │ │ +    client(Server_Node).
    │ │ │  
    │ │ │ -client(Server_Node) ->
    │ │ │ +client(Server_Node) ->
    │ │ │      receive
    │ │ │          logoff ->
    │ │ │ -            exit(normal);
    │ │ │ -        {message_to, ToName, Message} ->
    │ │ │ -            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
    │ │ │ -            await_result();
    │ │ │ -        {message_from, FromName, Message} ->
    │ │ │ -            io:format("Message from ~p: ~p~n", [FromName, Message])
    │ │ │ +            exit(normal);
    │ │ │ +        {message_to, ToName, Message} ->
    │ │ │ +            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
    │ │ │ +            await_result();
    │ │ │ +        {message_from, FromName, Message} ->
    │ │ │ +            io:format("Message from ~p: ~p~n", [FromName, Message])
    │ │ │      end,
    │ │ │ -    client(Server_Node).
    │ │ │ +    client(Server_Node).
    │ │ │  
    │ │ │  %%% wait for a response from the server
    │ │ │ -await_result() ->
    │ │ │ +await_result() ->
    │ │ │      receive
    │ │ │ -        {messenger, stop, Why} -> % Stop the client
    │ │ │ -            io:format("~p~n", [Why]),
    │ │ │ -            exit(normal);
    │ │ │ -        {messenger, What} ->  % Normal response
    │ │ │ -            io:format("~p~n", [What])
    │ │ │ +        {messenger, stop, Why} -> % Stop the client
    │ │ │ +            io:format("~p~n", [Why]),
    │ │ │ +            exit(normal);
    │ │ │ +        {messenger, What} ->  % Normal response
    │ │ │ +            io:format("~p~n", [What])
    │ │ │      after 5000 ->
    │ │ │ -            io:format("No response from server~n", []),
    │ │ │ -            exit(timeout)
    │ │ │ +            io:format("No response from server~n", []),
    │ │ │ +            exit(timeout)
    │ │ │      end.

    The following changes are added:

    The messenger server traps exits. If it receives an exit signal, │ │ │ {'EXIT',From,Reason}, this means that a client process has terminated or is │ │ │ unreachable for one of the following reasons:

    • The user has logged off (the "logoff" message is removed).
    • The network connection to the client is broken.
    • The node on which the client process resides has gone down.
    • The client processes has done some illegal operation.

    If an exit signal is received as above, the tuple {From,Name} is deleted from │ │ │ the servers User_List using the server_logoff function. If the node on which │ │ │ the server runs goes down, an exit signal (automatically generated by the │ │ │ system) is sent to all of the client processes: │ │ │ {'EXIT',MessengerPID,noconnection} causing all the client processes to │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/seq_prog.html │ │ │ @@ -136,293 +136,293 @@ │ │ │ 7 │ │ │ 2>

    As shown, the Erlang shell numbers the lines that can be entered, (as 1> 2>) and │ │ │ that it correctly says that 2 + 5 is 7. If you make writing mistakes in the │ │ │ shell, you can delete with the backspace key, as in most shells. There are many │ │ │ more editing commands in the shell (see │ │ │ tty - A command line interface in ERTS User's Guide).

    (Notice that many line numbers given by the shell in the following examples are │ │ │ out of sequence. This is because this tutorial was written and code-tested in │ │ │ -separate sessions).

    Here is a bit more complex calculation:

    2> (42 + 77) * 66 / 3.
    │ │ │ +separate sessions).

    Here is a bit more complex calculation:

    2> (42 + 77) * 66 / 3.
    │ │ │  2618.0

    Notice the use of brackets, the multiplication operator *, and the division │ │ │ operator /, as in normal arithmetic (see │ │ │ Expressions).

    Press Control-C to shut down the Erlang system and the Erlang shell.

    The following output is shown:

    BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
    │ │ │         (v)ersion (k)ill (D)b-tables (d)istribution
    │ │ │  a
    │ │ │ -$

    Type a to leave the Erlang system.

    Another way to shut down the Erlang system is by entering halt/0:

    3> halt().
    │ │ │ +$

    Type a to leave the Erlang system.

    Another way to shut down the Erlang system is by entering halt/0:

    3> halt().
    │ │ │  $

    │ │ │ │ │ │ │ │ │ │ │ │ Modules and Functions │ │ │

    │ │ │

    A programming language is not much use if you only can run code from the shell. │ │ │ So here is a small Erlang program. Enter it into a file named tut.erl using a │ │ │ suitable text editor. The file name tut.erl is important, and also that it is │ │ │ in the same directory as the one where you started erl). If you are lucky your │ │ │ editor has an Erlang mode that makes it easier for you to enter and format your │ │ │ code nicely (see The Erlang mode for Emacs │ │ │ in Tools User's Guide), but you can manage perfectly well without. Here is the │ │ │ -code to enter:

    -module(tut).
    │ │ │ --export([double/1]).
    │ │ │ +code to enter:

    -module(tut).
    │ │ │ +-export([double/1]).
    │ │ │  
    │ │ │ -double(X) ->
    │ │ │ +double(X) ->
    │ │ │      2 * X.

    It is not hard to guess that this program doubles the value of numbers. The │ │ │ first two lines of the code are described later. Let us compile the program. │ │ │ -This can be done in an Erlang shell as follows, where c means compile:

    3> c(tut).
    │ │ │ -{ok,tut}

    The {ok,tut} means that the compilation is OK. If it says error it means │ │ │ +This can be done in an Erlang shell as follows, where c means compile:

    3> c(tut).
    │ │ │ +{ok,tut}

    The {ok,tut} means that the compilation is OK. If it says error it means │ │ │ that there is some mistake in the text that you entered. Additional error │ │ │ messages gives an idea to what is wrong so you can modify the text and then try │ │ │ -to compile the program again.

    Now run the program:

    4> tut:double(10).
    │ │ │ +to compile the program again.

    Now run the program:

    4> tut:double(10).
    │ │ │  20

    As expected, double of 10 is 20.

    Now let us get back to the first two lines of the code. Erlang programs are │ │ │ written in files. Each file contains an Erlang module. The first line of code │ │ │ -in the module is the module name (see Modules):

    -module(tut).

    Thus, the module is called tut. Notice the full stop . at the end of the │ │ │ +in the module is the module name (see Modules):

    -module(tut).

    Thus, the module is called tut. Notice the full stop . at the end of the │ │ │ line. The files which are used to store the module must have the same name as │ │ │ the module but with the extension .erl. In this case the file name is │ │ │ tut.erl. When using a function in another module, the syntax │ │ │ module_name:function_name(arguments) is used. So the following means call │ │ │ -function double in module tut with argument 10.

    4> tut:double(10).

    The second line says that the module tut contains a function called double, │ │ │ -which takes one argument (X in our example):

    -export([double/1]).

    The second line also says that this function can be called from outside the │ │ │ +function double in module tut with argument 10.

    4> tut:double(10).

    The second line says that the module tut contains a function called double, │ │ │ +which takes one argument (X in our example):

    -export([double/1]).

    The second line also says that this function can be called from outside the │ │ │ module tut. More about this later. Again, notice the . at the end of the │ │ │ line.

    Now for a more complicated example, the factorial of a number. For example, the │ │ │ -factorial of 4 is 4 3 2 * 1, which equals 24.

    Enter the following code in a file named tut1.erl:

    -module(tut1).
    │ │ │ --export([fac/1]).
    │ │ │ +factorial of 4 is 4  3  2 * 1, which equals 24.

    Enter the following code in a file named tut1.erl:

    -module(tut1).
    │ │ │ +-export([fac/1]).
    │ │ │  
    │ │ │ -fac(1) ->
    │ │ │ +fac(1) ->
    │ │ │      1;
    │ │ │ -fac(N) ->
    │ │ │ -    N * fac(N - 1).

    So this is a module, called tut1 that contains a function called fac>, which │ │ │ -takes one argument, N.

    The first part says that the factorial of 1 is 1.:

    fac(1) ->
    │ │ │ +fac(N) ->
    │ │ │ +    N * fac(N - 1).

    So this is a module, called tut1 that contains a function called fac>, which │ │ │ +takes one argument, N.

    The first part says that the factorial of 1 is 1.:

    fac(1) ->
    │ │ │      1;

    Notice that this part ends with a semicolon ; that indicates that there is │ │ │ more of the function fac> to come.

    The second part says that the factorial of N is N multiplied by the factorial of │ │ │ -N - 1:

    fac(N) ->
    │ │ │ -    N * fac(N - 1).

    Notice that this part ends with a . saying that there are no more parts of │ │ │ -this function.

    Compile the file:

    5> c(tut1).
    │ │ │ -{ok,tut1}

    And now calculate the factorial of 4.

    6> tut1:fac(4).
    │ │ │ +N - 1:

    fac(N) ->
    │ │ │ +    N * fac(N - 1).

    Notice that this part ends with a . saying that there are no more parts of │ │ │ +this function.

    Compile the file:

    5> c(tut1).
    │ │ │ +{ok,tut1}

    And now calculate the factorial of 4.

    6> tut1:fac(4).
    │ │ │  24

    Here the function fac> in module tut1 is called with argument 4.

    A function can have many arguments. Let us expand the module tut1 with the │ │ │ -function to multiply two numbers:

    -module(tut1).
    │ │ │ --export([fac/1, mult/2]).
    │ │ │ +function to multiply two numbers:

    -module(tut1).
    │ │ │ +-export([fac/1, mult/2]).
    │ │ │  
    │ │ │ -fac(1) ->
    │ │ │ +fac(1) ->
    │ │ │      1;
    │ │ │ -fac(N) ->
    │ │ │ -    N * fac(N - 1).
    │ │ │ +fac(N) ->
    │ │ │ +    N * fac(N - 1).
    │ │ │  
    │ │ │ -mult(X, Y) ->
    │ │ │ +mult(X, Y) ->
    │ │ │      X * Y.

    Notice that it is also required to expand the -export line with the │ │ │ -information that there is another function mult with two arguments.

    Compile:

    7> c(tut1).
    │ │ │ -{ok,tut1}

    Try out the new function mult:

    8> tut1:mult(3,4).
    │ │ │ +information that there is another function mult with two arguments.

    Compile:

    7> c(tut1).
    │ │ │ +{ok,tut1}

    Try out the new function mult:

    8> tut1:mult(3,4).
    │ │ │  12

    In this example the numbers are integers and the arguments in the functions in │ │ │ the code N, X, and Y are called variables. Variables must start with a │ │ │ capital letter (see Variables). Examples of │ │ │ variables are Number, ShoeSize, and Age.

    │ │ │ │ │ │ │ │ │ │ │ │ Atoms │ │ │

    │ │ │

    Atom is another data type in Erlang. Atoms start with a small letter (see │ │ │ Atom), for example, charles, centimeter, and │ │ │ inch. Atoms are simply names, nothing else. They are not like variables, which │ │ │ can have a value.

    Enter the next program in a file named tut2.erl). It can be useful for │ │ │ -converting from inches to centimeters and conversely:

    -module(tut2).
    │ │ │ --export([convert/2]).
    │ │ │ +converting from inches to centimeters and conversely:

    -module(tut2).
    │ │ │ +-export([convert/2]).
    │ │ │  
    │ │ │ -convert(M, inch) ->
    │ │ │ +convert(M, inch) ->
    │ │ │      M / 2.54;
    │ │ │  
    │ │ │ -convert(N, centimeter) ->
    │ │ │ -    N * 2.54.

    Compile:

    9> c(tut2).
    │ │ │ -{ok,tut2}

    Test:

    10> tut2:convert(3, inch).
    │ │ │ +convert(N, centimeter) ->
    │ │ │ +    N * 2.54.

    Compile:

    9> c(tut2).
    │ │ │ +{ok,tut2}

    Test:

    10> tut2:convert(3, inch).
    │ │ │  1.1811023622047243
    │ │ │ -11> tut2:convert(7, centimeter).
    │ │ │ +11> tut2:convert(7, centimeter).
    │ │ │  17.78

    Notice the introduction of decimals (floating point numbers) without any │ │ │ explanation. Hopefully you can cope with that.

    Let us see what happens if something other than centimeter or inch is │ │ │ -entered in the convert function:

    12> tut2:convert(3, miles).
    │ │ │ +entered in the convert function:

    12> tut2:convert(3, miles).
    │ │ │  ** exception error: no function clause matching tut2:convert(3,miles) (tut2.erl, line 4)

    The two parts of the convert function are called its clauses. As shown, │ │ │ miles is not part of either of the clauses. The Erlang system cannot match │ │ │ either of the clauses so an error message function_clause is returned. The │ │ │ shell formats the error message nicely, but the error tuple is saved in the │ │ │ -shell's history list and can be output by the shell command v/1:

    13> v(12).
    │ │ │ -{'EXIT',{function_clause,[{tut2,convert,
    │ │ │ -                                [3,miles],
    │ │ │ -                                [{file,"tut2.erl"},{line,4}]},
    │ │ │ -                          {erl_eval,do_apply,6,
    │ │ │ -                                    [{file,"erl_eval.erl"},{line,677}]},
    │ │ │ -                          {shell,exprs,7,[{file,"shell.erl"},{line,687}]},
    │ │ │ -                          {shell,eval_exprs,7,[{file,"shell.erl"},{line,642}]},
    │ │ │ -                          {shell,eval_loop,3,
    │ │ │ -                                 [{file,"shell.erl"},{line,627}]}]}}

    │ │ │ +shell's history list and can be output by the shell command v/1:

    13> v(12).
    │ │ │ +{'EXIT',{function_clause,[{tut2,convert,
    │ │ │ +                                [3,miles],
    │ │ │ +                                [{file,"tut2.erl"},{line,4}]},
    │ │ │ +                          {erl_eval,do_apply,6,
    │ │ │ +                                    [{file,"erl_eval.erl"},{line,677}]},
    │ │ │ +                          {shell,exprs,7,[{file,"shell.erl"},{line,687}]},
    │ │ │ +                          {shell,eval_exprs,7,[{file,"shell.erl"},{line,642}]},
    │ │ │ +                          {shell,eval_loop,3,
    │ │ │ +                                 [{file,"shell.erl"},{line,627}]}]}}

    │ │ │ │ │ │ │ │ │ │ │ │ Tuples │ │ │

    │ │ │ -

    Now the tut2 program is hardly good programming style. Consider:

    tut2:convert(3, inch).

    Does this mean that 3 is in inches? Or does it mean that 3 is in centimeters and │ │ │ +

    Now the tut2 program is hardly good programming style. Consider:

    tut2:convert(3, inch).

    Does this mean that 3 is in inches? Or does it mean that 3 is in centimeters and │ │ │ is to be converted to inches? Erlang has a way to group things together to make │ │ │ things more understandable. These are called tuples and are surrounded by │ │ │ curly brackets, { and }.

    So, {inch,3} denotes 3 inches and {centimeter,5} denotes 5 centimeters. Now │ │ │ let us write a new program that converts centimeters to inches and conversely. │ │ │ -Enter the following code in a file called tut3.erl):

    -module(tut3).
    │ │ │ --export([convert_length/1]).
    │ │ │ +Enter the following code in a file called tut3.erl):

    -module(tut3).
    │ │ │ +-export([convert_length/1]).
    │ │ │  
    │ │ │ -convert_length({centimeter, X}) ->
    │ │ │ -    {inch, X / 2.54};
    │ │ │ -convert_length({inch, Y}) ->
    │ │ │ -    {centimeter, Y * 2.54}.

    Compile and test:

    14> c(tut3).
    │ │ │ -{ok,tut3}
    │ │ │ -15> tut3:convert_length({inch, 5}).
    │ │ │ -{centimeter,12.7}
    │ │ │ -16> tut3:convert_length(tut3:convert_length({inch, 5})).
    │ │ │ -{inch,5.0}

    Notice on line 16 that 5 inches is converted to centimeters and back again and │ │ │ +convert_length({centimeter, X}) -> │ │ │ + {inch, X / 2.54}; │ │ │ +convert_length({inch, Y}) -> │ │ │ + {centimeter, Y * 2.54}.

    Compile and test:

    14> c(tut3).
    │ │ │ +{ok,tut3}
    │ │ │ +15> tut3:convert_length({inch, 5}).
    │ │ │ +{centimeter,12.7}
    │ │ │ +16> tut3:convert_length(tut3:convert_length({inch, 5})).
    │ │ │ +{inch,5.0}

    Notice on line 16 that 5 inches is converted to centimeters and back again and │ │ │ reassuringly get back to the original value. That is, the argument to a function │ │ │ can be the result of another function. Consider how line 16 (above) works. The │ │ │ argument given to the function {inch,5} is first matched against the first │ │ │ head clause of convert_length, that is, convert_length({centimeter,X}). It │ │ │ can be seen that {centimeter,X} does not match {inch,5} (the head is the bit │ │ │ before the ->). This having failed, let us try the head of the next clause │ │ │ that is, convert_length({inch,Y}). This matches, and Y gets the value 5.

    Tuples can have more than two parts, in fact as many parts as you want, and │ │ │ contain any valid Erlang term. For example, to represent the temperature of │ │ │ -various cities of the world:

    {moscow, {c, -10}}
    │ │ │ -{cape_town, {f, 70}}
    │ │ │ -{paris, {f, 28}}

    Tuples have a fixed number of items in them. Each item in a tuple is called an │ │ │ +various cities of the world:

    {moscow, {c, -10}}
    │ │ │ +{cape_town, {f, 70}}
    │ │ │ +{paris, {f, 28}}

    Tuples have a fixed number of items in them. Each item in a tuple is called an │ │ │ element. In the tuple {moscow,{c,-10}}, element 1 is moscow and element 2 │ │ │ is {c,-10}. Here c represents Celsius and f Fahrenheit.

    │ │ │ │ │ │ │ │ │ │ │ │ Lists │ │ │

    │ │ │

    Whereas tuples group things together, it is also needed to represent lists of │ │ │ things. Lists in Erlang are surrounded by square brackets, [ and ]. For │ │ │ -example, a list of the temperatures of various cities in the world can be:

    [{moscow, {c, -10}}, {cape_town, {f, 70}}, {stockholm, {c, -4}},
    │ │ │ - {paris, {f, 28}}, {london, {f, 36}}]

    Notice that this list was so long that it did not fit on one line. This does not │ │ │ +example, a list of the temperatures of various cities in the world can be:

    [{moscow, {c, -10}}, {cape_town, {f, 70}}, {stockholm, {c, -4}},
    │ │ │ + {paris, {f, 28}}, {london, {f, 36}}]

    Notice that this list was so long that it did not fit on one line. This does not │ │ │ matter, Erlang allows line breaks at all "sensible places" but not, for example, │ │ │ in the middle of atoms, integers, and others.

    A useful way of looking at parts of lists, is by using |. This is best │ │ │ -explained by an example using the shell:

    17> [First |TheRest] = [1,2,3,4,5].
    │ │ │ -[1,2,3,4,5]
    │ │ │ +explained by an example using the shell:

    17> [First |TheRest] = [1,2,3,4,5].
    │ │ │ +[1,2,3,4,5]
    │ │ │  18> First.
    │ │ │  1
    │ │ │  19> TheRest.
    │ │ │ -[2,3,4,5]

    To separate the first elements of the list from the rest of the list, | is │ │ │ -used. First has got value 1 and TheRest has got the value [2,3,4,5].

    Another example:

    20> [E1, E2 | R] = [1,2,3,4,5,6,7].
    │ │ │ -[1,2,3,4,5,6,7]
    │ │ │ +[2,3,4,5]

    To separate the first elements of the list from the rest of the list, | is │ │ │ +used. First has got value 1 and TheRest has got the value [2,3,4,5].

    Another example:

    20> [E1, E2 | R] = [1,2,3,4,5,6,7].
    │ │ │ +[1,2,3,4,5,6,7]
    │ │ │  21> E1.
    │ │ │  1
    │ │ │  22> E2.
    │ │ │  2
    │ │ │  23> R.
    │ │ │ -[3,4,5,6,7]

    Here you see the use of | to get the first two elements from the list. If you │ │ │ +[3,4,5,6,7]

    Here you see the use of | to get the first two elements from the list. If you │ │ │ try to get more elements from the list than there are elements in the list, an │ │ │ error is returned. Notice also the special case of the list with no elements, │ │ │ -[]:

    24> [A, B | C] = [1, 2].
    │ │ │ -[1,2]
    │ │ │ +[]:

    24> [A, B | C] = [1, 2].
    │ │ │ +[1,2]
    │ │ │  25> A.
    │ │ │  1
    │ │ │  26> B.
    │ │ │  2
    │ │ │  27> C.
    │ │ │ -[]

    In the previous examples, new variable names are used, instead of reusing the │ │ │ +[]

    In the previous examples, new variable names are used, instead of reusing the │ │ │ old ones: First, TheRest, E1, E2, R, A, B, and C. The reason for │ │ │ this is that a variable can only be given a value once in its context (scope). │ │ │ More about this later.

    The following example shows how to find the length of a list. Enter the │ │ │ -following code in a file named tut4.erl:

    -module(tut4).
    │ │ │ +following code in a file named tut4.erl:

    -module(tut4).
    │ │ │  
    │ │ │ --export([list_length/1]).
    │ │ │ +-export([list_length/1]).
    │ │ │  
    │ │ │ -list_length([]) ->
    │ │ │ +list_length([]) ->
    │ │ │      0;
    │ │ │ -list_length([First | Rest]) ->
    │ │ │ -    1 + list_length(Rest).

    Compile and test:

    28> c(tut4).
    │ │ │ -{ok,tut4}
    │ │ │ -29> tut4:list_length([1,2,3,4,5,6,7]).
    │ │ │ -7

    Explanation:

    list_length([]) ->
    │ │ │ -    0;

    The length of an empty list is obviously 0.

    list_length([First | Rest]) ->
    │ │ │ -    1 + list_length(Rest).

    The length of a list with the first element First and the remaining elements │ │ │ +list_length([First | Rest]) -> │ │ │ + 1 + list_length(Rest).

    Compile and test:

    28> c(tut4).
    │ │ │ +{ok,tut4}
    │ │ │ +29> tut4:list_length([1,2,3,4,5,6,7]).
    │ │ │ +7

    Explanation:

    list_length([]) ->
    │ │ │ +    0;

    The length of an empty list is obviously 0.

    list_length([First | Rest]) ->
    │ │ │ +    1 + list_length(Rest).

    The length of a list with the first element First and the remaining elements │ │ │ Rest is 1 + the length of Rest.

    (Advanced readers only: This is not tail recursive, there is a better way to │ │ │ write this function.)

    In general, tuples are used where "records" or "structs" are used in other │ │ │ languages. Also, lists are used when representing things with varying sizes, │ │ │ that is, where linked lists are used in other languages.

    Erlang does not have a string data type. Instead, strings can be represented by │ │ │ lists of Unicode characters. This implies for example that the list [97,98,99] │ │ │ is equivalent to "abc". The Erlang shell is "clever" and guesses what list you │ │ │ -mean and outputs it in what it thinks is the most appropriate form, for example:

    30> [97,98,99].
    │ │ │ +mean and outputs it in what it thinks is the most appropriate form, for example:

    30> [97,98,99].
    │ │ │  "abc"

    │ │ │ │ │ │ │ │ │ │ │ │ Maps │ │ │

    │ │ │

    Maps are a set of key to value associations. These associations are encapsulated │ │ │ -with #{ and }. To create an association from "key" to value 42:

    > #{ "key" => 42 }.
    │ │ │ -#{"key" => 42}

    Let us jump straight into the deep end with an example using some interesting │ │ │ +with #{ and }. To create an association from "key" to value 42:

    > #{ "key" => 42 }.
    │ │ │ +#{"key" => 42}

    Let us jump straight into the deep end with an example using some interesting │ │ │ features.

    The following example shows how to calculate alpha blending using maps to │ │ │ -reference color and alpha channels. Enter the code in a file named color.erl):

    -module(color).
    │ │ │ +reference color and alpha channels. Enter the code in a file named color.erl):

    -module(color).
    │ │ │  
    │ │ │ --export([new/4, blend/2]).
    │ │ │ +-export([new/4, blend/2]).
    │ │ │  
    │ │ │ --define(is_channel(V), (is_float(V) andalso V >= 0.0 andalso V =< 1.0)).
    │ │ │ +-define(is_channel(V), (is_float(V) andalso V >= 0.0 andalso V =< 1.0)).
    │ │ │  
    │ │ │ -new(R,G,B,A) when ?is_channel(R), ?is_channel(G),
    │ │ │ -                  ?is_channel(B), ?is_channel(A) ->
    │ │ │ -    #{red => R, green => G, blue => B, alpha => A}.
    │ │ │ -
    │ │ │ -blend(Src,Dst) ->
    │ │ │ -    blend(Src,Dst,alpha(Src,Dst)).
    │ │ │ -
    │ │ │ -blend(Src,Dst,Alpha) when Alpha > 0.0 ->
    │ │ │ -    Dst#{
    │ │ │ -        red   := red(Src,Dst) / Alpha,
    │ │ │ -        green := green(Src,Dst) / Alpha,
    │ │ │ -        blue  := blue(Src,Dst) / Alpha,
    │ │ │ +new(R,G,B,A) when ?is_channel(R), ?is_channel(G),
    │ │ │ +                  ?is_channel(B), ?is_channel(A) ->
    │ │ │ +    #{red => R, green => G, blue => B, alpha => A}.
    │ │ │ +
    │ │ │ +blend(Src,Dst) ->
    │ │ │ +    blend(Src,Dst,alpha(Src,Dst)).
    │ │ │ +
    │ │ │ +blend(Src,Dst,Alpha) when Alpha > 0.0 ->
    │ │ │ +    Dst#{
    │ │ │ +        red   := red(Src,Dst) / Alpha,
    │ │ │ +        green := green(Src,Dst) / Alpha,
    │ │ │ +        blue  := blue(Src,Dst) / Alpha,
    │ │ │          alpha := Alpha
    │ │ │ -    };
    │ │ │ -blend(_,Dst,_) ->
    │ │ │ -    Dst#{
    │ │ │ +    };
    │ │ │ +blend(_,Dst,_) ->
    │ │ │ +    Dst#{
    │ │ │          red   := 0.0,
    │ │ │          green := 0.0,
    │ │ │          blue  := 0.0,
    │ │ │          alpha := 0.0
    │ │ │ -    }.
    │ │ │ +    }.
    │ │ │  
    │ │ │ -alpha(#{alpha := SA}, #{alpha := DA}) ->
    │ │ │ -    SA + DA*(1.0 - SA).
    │ │ │ +alpha(#{alpha := SA}, #{alpha := DA}) ->
    │ │ │ +    SA + DA*(1.0 - SA).
    │ │ │  
    │ │ │ -red(#{red := SV, alpha := SA}, #{red := DV, alpha := DA}) ->
    │ │ │ -    SV*SA + DV*DA*(1.0 - SA).
    │ │ │ -green(#{green := SV, alpha := SA}, #{green := DV, alpha := DA}) ->
    │ │ │ -    SV*SA + DV*DA*(1.0 - SA).
    │ │ │ -blue(#{blue := SV, alpha := SA}, #{blue := DV, alpha := DA}) ->
    │ │ │ -    SV*SA + DV*DA*(1.0 - SA).

    Compile and test:

    > c(color).
    │ │ │ -{ok,color}
    │ │ │ -> C1 = color:new(0.3,0.4,0.5,1.0).
    │ │ │ -#{alpha => 1.0,blue => 0.5,green => 0.4,red => 0.3}
    │ │ │ -> C2 = color:new(1.0,0.8,0.1,0.3).
    │ │ │ -#{alpha => 0.3,blue => 0.1,green => 0.8,red => 1.0}
    │ │ │ -> color:blend(C1,C2).
    │ │ │ -#{alpha => 1.0,blue => 0.5,green => 0.4,red => 0.3}
    │ │ │ -> color:blend(C2,C1).
    │ │ │ -#{alpha => 1.0,blue => 0.38,green => 0.52,red => 0.51}

    This example warrants some explanation:

    -define(is_channel(V), (is_float(V) andalso V >= 0.0 andalso V =< 1.0)).

    First a macro is_channel is defined to help with the guard tests. This is only │ │ │ +red(#{red := SV, alpha := SA}, #{red := DV, alpha := DA}) -> │ │ │ + SV*SA + DV*DA*(1.0 - SA). │ │ │ +green(#{green := SV, alpha := SA}, #{green := DV, alpha := DA}) -> │ │ │ + SV*SA + DV*DA*(1.0 - SA). │ │ │ +blue(#{blue := SV, alpha := SA}, #{blue := DV, alpha := DA}) -> │ │ │ + SV*SA + DV*DA*(1.0 - SA).

    Compile and test:

    > c(color).
    │ │ │ +{ok,color}
    │ │ │ +> C1 = color:new(0.3,0.4,0.5,1.0).
    │ │ │ +#{alpha => 1.0,blue => 0.5,green => 0.4,red => 0.3}
    │ │ │ +> C2 = color:new(1.0,0.8,0.1,0.3).
    │ │ │ +#{alpha => 0.3,blue => 0.1,green => 0.8,red => 1.0}
    │ │ │ +> color:blend(C1,C2).
    │ │ │ +#{alpha => 1.0,blue => 0.5,green => 0.4,red => 0.3}
    │ │ │ +> color:blend(C2,C1).
    │ │ │ +#{alpha => 1.0,blue => 0.38,green => 0.52,red => 0.51}

    This example warrants some explanation:

    -define(is_channel(V), (is_float(V) andalso V >= 0.0 andalso V =< 1.0)).

    First a macro is_channel is defined to help with the guard tests. This is only │ │ │ here for convenience and to reduce syntax cluttering. For more information about │ │ │ -macros, see The Preprocessor.

    new(R,G,B,A) when ?is_channel(R), ?is_channel(G),
    │ │ │ -                  ?is_channel(B), ?is_channel(A) ->
    │ │ │ -    #{red => R, green => G, blue => B, alpha => A}.

    The function new/4 creates a new map term and lets the keys red, green, │ │ │ +macros, see The Preprocessor.

    new(R,G,B,A) when ?is_channel(R), ?is_channel(G),
    │ │ │ +                  ?is_channel(B), ?is_channel(A) ->
    │ │ │ +    #{red => R, green => G, blue => B, alpha => A}.

    The function new/4 creates a new map term and lets the keys red, green, │ │ │ blue, and alpha be associated with an initial value. In this case, only │ │ │ float values between and including 0.0 and 1.0 are allowed, as ensured by the │ │ │ ?is_channel/1 macro for each argument. Only the => operator is allowed when │ │ │ creating a new map.

    By calling blend/2 on any color term created by new/4, the resulting color │ │ │ -can be calculated as determined by the two map terms.

    The first thing blend/2 does is to calculate the resulting alpha channel:

    alpha(#{alpha := SA}, #{alpha := DA}) ->
    │ │ │ -    SA + DA*(1.0 - SA).

    The value associated with key alpha is fetched for both arguments using the │ │ │ +can be calculated as determined by the two map terms.

    The first thing blend/2 does is to calculate the resulting alpha channel:

    alpha(#{alpha := SA}, #{alpha := DA}) ->
    │ │ │ +    SA + DA*(1.0 - SA).

    The value associated with key alpha is fetched for both arguments using the │ │ │ := operator. The other keys in the map are ignored, only the key alpha is │ │ │ -required and checked for.

    This is also the case for functions red/2, blue/2, and green/2.

    red(#{red := SV, alpha := SA}, #{red := DV, alpha := DA}) ->
    │ │ │ -    SV*SA + DV*DA*(1.0 - SA).

    The difference here is that a check is made for two keys in each map argument. │ │ │ -The other keys are ignored.

    Finally, let us return the resulting color in blend/3:

    blend(Src,Dst,Alpha) when Alpha > 0.0 ->
    │ │ │ -    Dst#{
    │ │ │ -        red   := red(Src,Dst) / Alpha,
    │ │ │ -        green := green(Src,Dst) / Alpha,
    │ │ │ -        blue  := blue(Src,Dst) / Alpha,
    │ │ │ +required and checked for.

    This is also the case for functions red/2, blue/2, and green/2.

    red(#{red := SV, alpha := SA}, #{red := DV, alpha := DA}) ->
    │ │ │ +    SV*SA + DV*DA*(1.0 - SA).

    The difference here is that a check is made for two keys in each map argument. │ │ │ +The other keys are ignored.

    Finally, let us return the resulting color in blend/3:

    blend(Src,Dst,Alpha) when Alpha > 0.0 ->
    │ │ │ +    Dst#{
    │ │ │ +        red   := red(Src,Dst) / Alpha,
    │ │ │ +        green := green(Src,Dst) / Alpha,
    │ │ │ +        blue  := blue(Src,Dst) / Alpha,
    │ │ │          alpha := Alpha
    │ │ │ -    };

    The Dst map is updated with new channel values. The syntax for updating an │ │ │ + };

    The Dst map is updated with new channel values. The syntax for updating an │ │ │ existing key with a new value is with the := operator.

    │ │ │ │ │ │ │ │ │ │ │ │ Standard Modules and Manual Pages │ │ │

    │ │ │

    Erlang has many standard modules to help you do things. For example, the module │ │ │ @@ -442,24 +442,24 @@ │ │ │ │ │ │ │ │ │ │ │ │ Writing Output to a Terminal │ │ │

    │ │ │

    It is nice to be able to do formatted output in examples, so the next example │ │ │ shows a simple way to use the io:format/2 function. Like all other exported │ │ │ -functions, you can test the io:format/2 function in the shell:

    31> io:format("hello world~n", []).
    │ │ │ +functions, you can test the io:format/2 function in the shell:

    31> io:format("hello world~n", []).
    │ │ │  hello world
    │ │ │  ok
    │ │ │ -32> io:format("this outputs one Erlang term: ~w~n", [hello]).
    │ │ │ +32> io:format("this outputs one Erlang term: ~w~n", [hello]).
    │ │ │  this outputs one Erlang term: hello
    │ │ │  ok
    │ │ │ -33> io:format("this outputs two Erlang terms: ~w~w~n", [hello, world]).
    │ │ │ +33> io:format("this outputs two Erlang terms: ~w~w~n", [hello, world]).
    │ │ │  this outputs two Erlang terms: helloworld
    │ │ │  ok
    │ │ │ -34> io:format("this outputs two Erlang terms: ~w ~w~n", [hello, world]).
    │ │ │ +34> io:format("this outputs two Erlang terms: ~w ~w~n", [hello, world]).
    │ │ │  this outputs two Erlang terms: hello world
    │ │ │  ok

    The function io:format/2 (that is, format with two arguments) takes two lists. │ │ │ The first one is nearly always a list written between " ". This list is printed │ │ │ out as it is, except that each ~w is replaced by a term taken in order from the │ │ │ second list. Each ~n is replaced by a new line. The io:format/2 function │ │ │ itself returns the atom ok if everything goes as planned. Like other functions │ │ │ in Erlang, it crashes if an error occurs. This is not a fault in Erlang, it is a │ │ │ @@ -473,34 +473,34 @@ │ │ │ A Larger Example │ │ │ │ │ │

    Now for a larger example to consolidate what you have learnt so far. Assume that │ │ │ you have a list of temperature readings from a number of cities in the world. │ │ │ Some of them are in Celsius and some in Fahrenheit (as in the previous list). │ │ │ First let us convert them all to Celsius, then let us print the data neatly.

    %% This module is in file tut5.erl
    │ │ │  
    │ │ │ --module(tut5).
    │ │ │ --export([format_temps/1]).
    │ │ │ +-module(tut5).
    │ │ │ +-export([format_temps/1]).
    │ │ │  
    │ │ │  %% Only this function is exported
    │ │ │ -format_temps([])->                        % No output for an empty list
    │ │ │ +format_temps([])->                        % No output for an empty list
    │ │ │      ok;
    │ │ │ -format_temps([City | Rest]) ->
    │ │ │ -    print_temp(convert_to_celsius(City)),
    │ │ │ -    format_temps(Rest).
    │ │ │ -
    │ │ │ -convert_to_celsius({Name, {c, Temp}}) ->  % No conversion needed
    │ │ │ -    {Name, {c, Temp}};
    │ │ │ -convert_to_celsius({Name, {f, Temp}}) ->  % Do the conversion
    │ │ │ -    {Name, {c, (Temp - 32) * 5 / 9}}.
    │ │ │ -
    │ │ │ -print_temp({Name, {c, Temp}}) ->
    │ │ │ -    io:format("~-15w ~w c~n", [Name, Temp]).
    35> c(tut5).
    │ │ │ -{ok,tut5}
    │ │ │ -36> tut5:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ -{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │ +format_temps([City | Rest]) ->
    │ │ │ +    print_temp(convert_to_celsius(City)),
    │ │ │ +    format_temps(Rest).
    │ │ │ +
    │ │ │ +convert_to_celsius({Name, {c, Temp}}) ->  % No conversion needed
    │ │ │ +    {Name, {c, Temp}};
    │ │ │ +convert_to_celsius({Name, {f, Temp}}) ->  % Do the conversion
    │ │ │ +    {Name, {c, (Temp - 32) * 5 / 9}}.
    │ │ │ +
    │ │ │ +print_temp({Name, {c, Temp}}) ->
    │ │ │ +    io:format("~-15w ~w c~n", [Name, Temp]).
    35> c(tut5).
    │ │ │ +{ok,tut5}
    │ │ │ +36> tut5:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ +{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │  moscow          -10 c
    │ │ │  cape_town       21.11111111111111 c
    │ │ │  stockholm       -4 c
    │ │ │  paris           -2.2222222222222223 c
    │ │ │  london          2.2222222222222223 c
    │ │ │  ok

    Before looking at how this program works, notice that a few comments are added │ │ │ to the code. A comment starts with a %-character and goes on to the end of the │ │ │ @@ -528,28 +528,28 @@ │ │ │ │ │ │ │ │ │ │ │ │ Matching, Guards, and Scope of Variables │ │ │ │ │ │

    It can be useful to find the maximum and minimum temperature in lists like this. │ │ │ Before extending the program to do this, let us look at functions for finding │ │ │ -the maximum value of the elements in a list:

    -module(tut6).
    │ │ │ --export([list_max/1]).
    │ │ │ +the maximum value of the elements in a list:

    -module(tut6).
    │ │ │ +-export([list_max/1]).
    │ │ │  
    │ │ │ -list_max([Head|Rest]) ->
    │ │ │ -   list_max(Rest, Head).
    │ │ │ +list_max([Head|Rest]) ->
    │ │ │ +   list_max(Rest, Head).
    │ │ │  
    │ │ │ -list_max([], Res) ->
    │ │ │ +list_max([], Res) ->
    │ │ │      Res;
    │ │ │ -list_max([Head|Rest], Result_so_far) when Head > Result_so_far ->
    │ │ │ -    list_max(Rest, Head);
    │ │ │ -list_max([Head|Rest], Result_so_far)  ->
    │ │ │ -    list_max(Rest, Result_so_far).
    37> c(tut6).
    │ │ │ -{ok,tut6}
    │ │ │ -38> tut6:list_max([1,2,3,4,5,7,4,3,2,1]).
    │ │ │ +list_max([Head|Rest], Result_so_far) when Head > Result_so_far ->
    │ │ │ +    list_max(Rest, Head);
    │ │ │ +list_max([Head|Rest], Result_so_far)  ->
    │ │ │ +    list_max(Rest, Result_so_far).
    37> c(tut6).
    │ │ │ +{ok,tut6}
    │ │ │ +38> tut6:list_max([1,2,3,4,5,7,4,3,2,1]).
    │ │ │  7

    First notice that two functions have the same name, list_max. However, each of │ │ │ these takes a different number of arguments (parameters). In Erlang these are │ │ │ regarded as completely different functions. Where you need to distinguish │ │ │ between these functions, you write Name/Arity, where Name is the function name │ │ │ and Arity is the number of arguments, in this case list_max/1 and │ │ │ list_max/2.

    In this example you walk through a list "carrying" a value, in this case │ │ │ Result_so_far. list_max/1 simply assumes that the max value of the list is │ │ │ @@ -578,180 +578,180 @@ │ │ │ 5 │ │ │ 40> M = 6. │ │ │ ** exception error: no match of right hand side value 6 │ │ │ 41> M = M + 1. │ │ │ ** exception error: no match of right hand side value 6 │ │ │ 42> N = M + 1. │ │ │ 6

    The use of the match operator is particularly useful for pulling apart Erlang │ │ │ -terms and creating new ones.

    43> {X, Y} = {paris, {f, 28}}.
    │ │ │ -{paris,{f,28}}
    │ │ │ +terms and creating new ones.

    43> {X, Y} = {paris, {f, 28}}.
    │ │ │ +{paris,{f,28}}
    │ │ │  44> X.
    │ │ │  paris
    │ │ │  45> Y.
    │ │ │ -{f,28}

    Here X gets the value paris and Y the value {f,28}.

    If you try to do the same again with another city, an error is returned:

    46> {X, Y} = {london, {f, 36}}.
    │ │ │ +{f,28}

    Here X gets the value paris and Y the value {f,28}.

    If you try to do the same again with another city, an error is returned:

    46> {X, Y} = {london, {f, 36}}.
    │ │ │  ** exception error: no match of right hand side value {london,{f,36}}

    Variables can also be used to improve the readability of programs. For example, │ │ │ -in function list_max/2 above, you can write:

    list_max([Head|Rest], Result_so_far) when Head > Result_so_far ->
    │ │ │ +in function list_max/2 above, you can write:

    list_max([Head|Rest], Result_so_far) when Head > Result_so_far ->
    │ │ │      New_result_far = Head,
    │ │ │ -    list_max(Rest, New_result_far);

    This is possibly a little clearer.

    │ │ │ + list_max(Rest, New_result_far);

    This is possibly a little clearer.

    │ │ │ │ │ │ │ │ │ │ │ │ More About Lists │ │ │

    │ │ │ -

    Remember that the | operator can be used to get the head of a list:

    47> [M1|T1] = [paris, london, rome].
    │ │ │ -[paris,london,rome]
    │ │ │ +

    Remember that the | operator can be used to get the head of a list:

    47> [M1|T1] = [paris, london, rome].
    │ │ │ +[paris,london,rome]
    │ │ │  48> M1.
    │ │ │  paris
    │ │ │  49> T1.
    │ │ │ -[london,rome]

    The | operator can also be used to add a head to a list:

    50> L1 = [madrid | T1].
    │ │ │ -[madrid,london,rome]
    │ │ │ +[london,rome]

    The | operator can also be used to add a head to a list:

    50> L1 = [madrid | T1].
    │ │ │ +[madrid,london,rome]
    │ │ │  51> L1.
    │ │ │ -[madrid,london,rome]

    Now an example of this when working with lists - reversing the order of a list:

    -module(tut8).
    │ │ │ +[madrid,london,rome]

    Now an example of this when working with lists - reversing the order of a list:

    -module(tut8).
    │ │ │  
    │ │ │ --export([reverse/1]).
    │ │ │ +-export([reverse/1]).
    │ │ │  
    │ │ │ -reverse(List) ->
    │ │ │ -    reverse(List, []).
    │ │ │ +reverse(List) ->
    │ │ │ +    reverse(List, []).
    │ │ │  
    │ │ │ -reverse([Head | Rest], Reversed_List) ->
    │ │ │ -    reverse(Rest, [Head | Reversed_List]);
    │ │ │ -reverse([], Reversed_List) ->
    │ │ │ -    Reversed_List.
    52> c(tut8).
    │ │ │ -{ok,tut8}
    │ │ │ -53> tut8:reverse([1,2,3]).
    │ │ │ -[3,2,1]

    Consider how Reversed_List is built. It starts as [], then successively the │ │ │ +reverse([Head | Rest], Reversed_List) -> │ │ │ + reverse(Rest, [Head | Reversed_List]); │ │ │ +reverse([], Reversed_List) -> │ │ │ + Reversed_List.

    52> c(tut8).
    │ │ │ +{ok,tut8}
    │ │ │ +53> tut8:reverse([1,2,3]).
    │ │ │ +[3,2,1]

    Consider how Reversed_List is built. It starts as [], then successively the │ │ │ heads are taken off of the list to be reversed and added to the the │ │ │ -Reversed_List, as shown in the following:

    reverse([1|2,3], []) =>
    │ │ │ -    reverse([2,3], [1|[]])
    │ │ │ +Reversed_List, as shown in the following:

    reverse([1|2,3], []) =>
    │ │ │ +    reverse([2,3], [1|[]])
    │ │ │  
    │ │ │ -reverse([2|3], [1]) =>
    │ │ │ -    reverse([3], [2|[1]])
    │ │ │ +reverse([2|3], [1]) =>
    │ │ │ +    reverse([3], [2|[1]])
    │ │ │  
    │ │ │ -reverse([3|[]], [2,1]) =>
    │ │ │ -    reverse([], [3|[2,1]])
    │ │ │ +reverse([3|[]], [2,1]) =>
    │ │ │ +    reverse([], [3|[2,1]])
    │ │ │  
    │ │ │ -reverse([], [3,2,1]) =>
    │ │ │ -    [3,2,1]

    The module lists contains many functions for manipulating lists, for example, │ │ │ +reverse([], [3,2,1]) => │ │ │ + [3,2,1]

    The module lists contains many functions for manipulating lists, for example, │ │ │ for reversing them. So before writing a list-manipulating function it is a good │ │ │ idea to check if one not already is written for you (see the lists manual │ │ │ page in STDLIB).

    Now let us get back to the cities and temperatures, but take a more structured │ │ │ -approach this time. First let us convert the whole list to Celsius as follows:

    -module(tut7).
    │ │ │ --export([format_temps/1]).
    │ │ │ +approach this time. First let us convert the whole list to Celsius as follows:

    -module(tut7).
    │ │ │ +-export([format_temps/1]).
    │ │ │  
    │ │ │ -format_temps(List_of_cities) ->
    │ │ │ -    convert_list_to_c(List_of_cities).
    │ │ │ +format_temps(List_of_cities) ->
    │ │ │ +    convert_list_to_c(List_of_cities).
    │ │ │  
    │ │ │ -convert_list_to_c([{Name, {f, F}} | Rest]) ->
    │ │ │ -    Converted_City = {Name, {c, (F -32)* 5 / 9}},
    │ │ │ -    [Converted_City | convert_list_to_c(Rest)];
    │ │ │ -
    │ │ │ -convert_list_to_c([City | Rest]) ->
    │ │ │ -    [City | convert_list_to_c(Rest)];
    │ │ │ -
    │ │ │ -convert_list_to_c([]) ->
    │ │ │ -    [].

    Test the function:

    54> c(tut7).
    │ │ │ -{ok, tut7}.
    │ │ │ -55> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ -{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │ -[{moscow,{c,-10}},
    │ │ │ - {cape_town,{c,21.11111111111111}},
    │ │ │ - {stockholm,{c,-4}},
    │ │ │ - {paris,{c,-2.2222222222222223}},
    │ │ │ - {london,{c,2.2222222222222223}}]

    Explanation:

    format_temps(List_of_cities) ->
    │ │ │ -    convert_list_to_c(List_of_cities).

    Here format_temps/1 calls convert_list_to_c/1. convert_list_to_c/1 takes │ │ │ +convert_list_to_c([{Name, {f, F}} | Rest]) -> │ │ │ + Converted_City = {Name, {c, (F -32)* 5 / 9}}, │ │ │ + [Converted_City | convert_list_to_c(Rest)]; │ │ │ + │ │ │ +convert_list_to_c([City | Rest]) -> │ │ │ + [City | convert_list_to_c(Rest)]; │ │ │ + │ │ │ +convert_list_to_c([]) -> │ │ │ + [].

    Test the function:

    54> c(tut7).
    │ │ │ +{ok, tut7}.
    │ │ │ +55> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ +{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │ +[{moscow,{c,-10}},
    │ │ │ + {cape_town,{c,21.11111111111111}},
    │ │ │ + {stockholm,{c,-4}},
    │ │ │ + {paris,{c,-2.2222222222222223}},
    │ │ │ + {london,{c,2.2222222222222223}}]

    Explanation:

    format_temps(List_of_cities) ->
    │ │ │ +    convert_list_to_c(List_of_cities).

    Here format_temps/1 calls convert_list_to_c/1. convert_list_to_c/1 takes │ │ │ off the head of the List_of_cities, converts it to Celsius if needed. The | │ │ │ -operator is used to add the (maybe) converted to the converted rest of the list:

    [Converted_City | convert_list_to_c(Rest)];

    or:

    [City | convert_list_to_c(Rest)];

    This is done until the end of the list is reached, that is, the list is empty:

    convert_list_to_c([]) ->
    │ │ │ -    [].

    Now when the list is converted, a function to print it is added:

    -module(tut7).
    │ │ │ --export([format_temps/1]).
    │ │ │ -
    │ │ │ -format_temps(List_of_cities) ->
    │ │ │ -    Converted_List = convert_list_to_c(List_of_cities),
    │ │ │ -    print_temp(Converted_List).
    │ │ │ -
    │ │ │ -convert_list_to_c([{Name, {f, F}} | Rest]) ->
    │ │ │ -    Converted_City = {Name, {c, (F -32)* 5 / 9}},
    │ │ │ -    [Converted_City | convert_list_to_c(Rest)];
    │ │ │ -
    │ │ │ -convert_list_to_c([City | Rest]) ->
    │ │ │ -    [City | convert_list_to_c(Rest)];
    │ │ │ -
    │ │ │ -convert_list_to_c([]) ->
    │ │ │ -    [].
    │ │ │ -
    │ │ │ -print_temp([{Name, {c, Temp}} | Rest]) ->
    │ │ │ -    io:format("~-15w ~w c~n", [Name, Temp]),
    │ │ │ -    print_temp(Rest);
    │ │ │ -print_temp([]) ->
    │ │ │ -    ok.
    56> c(tut7).
    │ │ │ -{ok,tut7}
    │ │ │ -57> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ -{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │ +operator is used to add the (maybe) converted to the converted rest of the list:

    [Converted_City | convert_list_to_c(Rest)];

    or:

    [City | convert_list_to_c(Rest)];

    This is done until the end of the list is reached, that is, the list is empty:

    convert_list_to_c([]) ->
    │ │ │ +    [].

    Now when the list is converted, a function to print it is added:

    -module(tut7).
    │ │ │ +-export([format_temps/1]).
    │ │ │ +
    │ │ │ +format_temps(List_of_cities) ->
    │ │ │ +    Converted_List = convert_list_to_c(List_of_cities),
    │ │ │ +    print_temp(Converted_List).
    │ │ │ +
    │ │ │ +convert_list_to_c([{Name, {f, F}} | Rest]) ->
    │ │ │ +    Converted_City = {Name, {c, (F -32)* 5 / 9}},
    │ │ │ +    [Converted_City | convert_list_to_c(Rest)];
    │ │ │ +
    │ │ │ +convert_list_to_c([City | Rest]) ->
    │ │ │ +    [City | convert_list_to_c(Rest)];
    │ │ │ +
    │ │ │ +convert_list_to_c([]) ->
    │ │ │ +    [].
    │ │ │ +
    │ │ │ +print_temp([{Name, {c, Temp}} | Rest]) ->
    │ │ │ +    io:format("~-15w ~w c~n", [Name, Temp]),
    │ │ │ +    print_temp(Rest);
    │ │ │ +print_temp([]) ->
    │ │ │ +    ok.
    56> c(tut7).
    │ │ │ +{ok,tut7}
    │ │ │ +57> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ +{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │  moscow          -10 c
    │ │ │  cape_town       21.11111111111111 c
    │ │ │  stockholm       -4 c
    │ │ │  paris           -2.2222222222222223 c
    │ │ │  london          2.2222222222222223 c
    │ │ │  ok

    Now a function has to be added to find the cities with the maximum and minimum │ │ │ temperatures. The following program is not the most efficient way of doing this │ │ │ as you walk through the list of cities four times. But it is better to first │ │ │ strive for clarity and correctness and to make programs efficient only if │ │ │ -needed.

    -module(tut7).
    │ │ │ --export([format_temps/1]).
    │ │ │ +needed.

    -module(tut7).
    │ │ │ +-export([format_temps/1]).
    │ │ │  
    │ │ │ -format_temps(List_of_cities) ->
    │ │ │ -    Converted_List = convert_list_to_c(List_of_cities),
    │ │ │ -    print_temp(Converted_List),
    │ │ │ -    {Max_city, Min_city} = find_max_and_min(Converted_List),
    │ │ │ -    print_max_and_min(Max_city, Min_city).
    │ │ │ -
    │ │ │ -convert_list_to_c([{Name, {f, Temp}} | Rest]) ->
    │ │ │ -    Converted_City = {Name, {c, (Temp -32)* 5 / 9}},
    │ │ │ -    [Converted_City | convert_list_to_c(Rest)];
    │ │ │ -
    │ │ │ -convert_list_to_c([City | Rest]) ->
    │ │ │ -    [City | convert_list_to_c(Rest)];
    │ │ │ -
    │ │ │ -convert_list_to_c([]) ->
    │ │ │ -    [].
    │ │ │ -
    │ │ │ -print_temp([{Name, {c, Temp}} | Rest]) ->
    │ │ │ -    io:format("~-15w ~w c~n", [Name, Temp]),
    │ │ │ -    print_temp(Rest);
    │ │ │ -print_temp([]) ->
    │ │ │ +format_temps(List_of_cities) ->
    │ │ │ +    Converted_List = convert_list_to_c(List_of_cities),
    │ │ │ +    print_temp(Converted_List),
    │ │ │ +    {Max_city, Min_city} = find_max_and_min(Converted_List),
    │ │ │ +    print_max_and_min(Max_city, Min_city).
    │ │ │ +
    │ │ │ +convert_list_to_c([{Name, {f, Temp}} | Rest]) ->
    │ │ │ +    Converted_City = {Name, {c, (Temp -32)* 5 / 9}},
    │ │ │ +    [Converted_City | convert_list_to_c(Rest)];
    │ │ │ +
    │ │ │ +convert_list_to_c([City | Rest]) ->
    │ │ │ +    [City | convert_list_to_c(Rest)];
    │ │ │ +
    │ │ │ +convert_list_to_c([]) ->
    │ │ │ +    [].
    │ │ │ +
    │ │ │ +print_temp([{Name, {c, Temp}} | Rest]) ->
    │ │ │ +    io:format("~-15w ~w c~n", [Name, Temp]),
    │ │ │ +    print_temp(Rest);
    │ │ │ +print_temp([]) ->
    │ │ │      ok.
    │ │ │  
    │ │ │ -find_max_and_min([City | Rest]) ->
    │ │ │ -    find_max_and_min(Rest, City, City).
    │ │ │ +find_max_and_min([City | Rest]) ->
    │ │ │ +    find_max_and_min(Rest, City, City).
    │ │ │  
    │ │ │ -find_max_and_min([{Name, {c, Temp}} | Rest],
    │ │ │ -         {Max_Name, {c, Max_Temp}},
    │ │ │ -         {Min_Name, {c, Min_Temp}}) ->
    │ │ │ +find_max_and_min([{Name, {c, Temp}} | Rest],
    │ │ │ +         {Max_Name, {c, Max_Temp}},
    │ │ │ +         {Min_Name, {c, Min_Temp}}) ->
    │ │ │      if
    │ │ │          Temp > Max_Temp ->
    │ │ │ -            Max_City = {Name, {c, Temp}};           % Change
    │ │ │ +            Max_City = {Name, {c, Temp}};           % Change
    │ │ │          true ->
    │ │ │ -            Max_City = {Max_Name, {c, Max_Temp}} % Unchanged
    │ │ │ +            Max_City = {Max_Name, {c, Max_Temp}} % Unchanged
    │ │ │      end,
    │ │ │      if
    │ │ │           Temp < Min_Temp ->
    │ │ │ -            Min_City = {Name, {c, Temp}};           % Change
    │ │ │ +            Min_City = {Name, {c, Temp}};           % Change
    │ │ │          true ->
    │ │ │ -            Min_City = {Min_Name, {c, Min_Temp}} % Unchanged
    │ │ │ +            Min_City = {Min_Name, {c, Min_Temp}} % Unchanged
    │ │ │      end,
    │ │ │ -    find_max_and_min(Rest, Max_City, Min_City);
    │ │ │ +    find_max_and_min(Rest, Max_City, Min_City);
    │ │ │  
    │ │ │ -find_max_and_min([], Max_City, Min_City) ->
    │ │ │ -    {Max_City, Min_City}.
    │ │ │ +find_max_and_min([], Max_City, Min_City) ->
    │ │ │ +    {Max_City, Min_City}.
    │ │ │  
    │ │ │ -print_max_and_min({Max_name, {c, Max_temp}}, {Min_name, {c, Min_temp}}) ->
    │ │ │ -    io:format("Max temperature was ~w c in ~w~n", [Max_temp, Max_name]),
    │ │ │ -    io:format("Min temperature was ~w c in ~w~n", [Min_temp, Min_name]).
    58> c(tut7).
    │ │ │ -{ok, tut7}
    │ │ │ -59> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ -{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │ +print_max_and_min({Max_name, {c, Max_temp}}, {Min_name, {c, Min_temp}}) ->
    │ │ │ +    io:format("Max temperature was ~w c in ~w~n", [Max_temp, Max_name]),
    │ │ │ +    io:format("Min temperature was ~w c in ~w~n", [Min_temp, Min_name]).
    58> c(tut7).
    │ │ │ +{ok, tut7}
    │ │ │ +59> tut7:format_temps([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ +{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │  moscow          -10 c
    │ │ │  cape_town       21.11111111111111 c
    │ │ │  stockholm       -4 c
    │ │ │  paris           -2.2222222222222223 c
    │ │ │  london          2.2222222222222223 c
    │ │ │  Max temperature was 21.11111111111111 c in cape_town
    │ │ │  Min temperature was -10 c in moscow
    │ │ │ @@ -773,88 +773,88 @@
    │ │ │          Action 4
    │ │ │  end

    Notice that there is no ; before end. Conditions do the same as guards, that │ │ │ is, tests that succeed or fail. Erlang starts at the top and tests until it │ │ │ finds a condition that succeeds. Then it evaluates (performs) the action │ │ │ following the condition and ignores all other conditions and actions before the │ │ │ end. If no condition matches, a run-time failure occurs. A condition that │ │ │ always succeeds is the atom true. This is often used last in an if, meaning, │ │ │ -do the action following the true if all other conditions have failed.

    The following is a short program to show the workings of if.

    -module(tut9).
    │ │ │ --export([test_if/2]).
    │ │ │ +do the action following the true if all other conditions have failed.

    The following is a short program to show the workings of if.

    -module(tut9).
    │ │ │ +-export([test_if/2]).
    │ │ │  
    │ │ │ -test_if(A, B) ->
    │ │ │ +test_if(A, B) ->
    │ │ │      if
    │ │ │          A == 5 ->
    │ │ │ -            io:format("A == 5~n", []),
    │ │ │ +            io:format("A == 5~n", []),
    │ │ │              a_equals_5;
    │ │ │          B == 6 ->
    │ │ │ -            io:format("B == 6~n", []),
    │ │ │ +            io:format("B == 6~n", []),
    │ │ │              b_equals_6;
    │ │ │          A == 2, B == 3 ->                      %That is A equals 2 and B equals 3
    │ │ │ -            io:format("A == 2, B == 3~n", []),
    │ │ │ +            io:format("A == 2, B == 3~n", []),
    │ │ │              a_equals_2_b_equals_3;
    │ │ │          A == 1 ; B == 7 ->                     %That is A equals 1 or B equals 7
    │ │ │ -            io:format("A == 1 ; B == 7~n", []),
    │ │ │ +            io:format("A == 1 ; B == 7~n", []),
    │ │ │              a_equals_1_or_b_equals_7
    │ │ │ -    end.

    Testing this program gives:

    60> c(tut9).
    │ │ │ -{ok,tut9}
    │ │ │ -61> tut9:test_if(5,33).
    │ │ │ +    end.

    Testing this program gives:

    60> c(tut9).
    │ │ │ +{ok,tut9}
    │ │ │ +61> tut9:test_if(5,33).
    │ │ │  A == 5
    │ │ │  a_equals_5
    │ │ │ -62> tut9:test_if(33,6).
    │ │ │ +62> tut9:test_if(33,6).
    │ │ │  B == 6
    │ │ │  b_equals_6
    │ │ │ -63> tut9:test_if(2, 3).
    │ │ │ +63> tut9:test_if(2, 3).
    │ │ │  A == 2, B == 3
    │ │ │  a_equals_2_b_equals_3
    │ │ │ -64> tut9:test_if(1, 33).
    │ │ │ +64> tut9:test_if(1, 33).
    │ │ │  A == 1 ; B == 7
    │ │ │  a_equals_1_or_b_equals_7
    │ │ │ -65> tut9:test_if(33, 7).
    │ │ │ +65> tut9:test_if(33, 7).
    │ │ │  A == 1 ; B == 7
    │ │ │  a_equals_1_or_b_equals_7
    │ │ │ -66> tut9:test_if(33, 33).
    │ │ │ +66> tut9:test_if(33, 33).
    │ │ │  ** exception error: no true branch found when evaluating an if expression
    │ │ │       in function  tut9:test_if/2 (tut9.erl, line 5)

    Notice that tut9:test_if(33,33) does not cause any condition to succeed. This │ │ │ leads to the run time error if_clause, here nicely formatted by the shell. See │ │ │ Guard Sequences for details of the many guard tests │ │ │ available.

    case is another construct in Erlang. Recall that the convert_length function │ │ │ -was written as:

    convert_length({centimeter, X}) ->
    │ │ │ -    {inch, X / 2.54};
    │ │ │ -convert_length({inch, Y}) ->
    │ │ │ -    {centimeter, Y * 2.54}.

    The same program can also be written as:

    -module(tut10).
    │ │ │ --export([convert_length/1]).
    │ │ │ +was written as:

    convert_length({centimeter, X}) ->
    │ │ │ +    {inch, X / 2.54};
    │ │ │ +convert_length({inch, Y}) ->
    │ │ │ +    {centimeter, Y * 2.54}.

    The same program can also be written as:

    -module(tut10).
    │ │ │ +-export([convert_length/1]).
    │ │ │  
    │ │ │ -convert_length(Length) ->
    │ │ │ +convert_length(Length) ->
    │ │ │      case Length of
    │ │ │ -        {centimeter, X} ->
    │ │ │ -            {inch, X / 2.54};
    │ │ │ -        {inch, Y} ->
    │ │ │ -            {centimeter, Y * 2.54}
    │ │ │ -    end.
    67> c(tut10).
    │ │ │ -{ok,tut10}
    │ │ │ -68> tut10:convert_length({inch, 6}).
    │ │ │ -{centimeter,15.24}
    │ │ │ -69> tut10:convert_length({centimeter, 2.5}).
    │ │ │ -{inch,0.984251968503937}

    Both case and if have return values, that is, in the above example case │ │ │ + {centimeter, X} -> │ │ │ + {inch, X / 2.54}; │ │ │ + {inch, Y} -> │ │ │ + {centimeter, Y * 2.54} │ │ │ + end.

    67> c(tut10).
    │ │ │ +{ok,tut10}
    │ │ │ +68> tut10:convert_length({inch, 6}).
    │ │ │ +{centimeter,15.24}
    │ │ │ +69> tut10:convert_length({centimeter, 2.5}).
    │ │ │ +{inch,0.984251968503937}

    Both case and if have return values, that is, in the above example case │ │ │ returned either {inch,X/2.54} or {centimeter,Y*2.54}. The behaviour of │ │ │ case can also be modified by using guards. The following example clarifies │ │ │ this. It tells us the length of a month, given the year. The year must be known, │ │ │ -since February has 29 days in a leap year.

    -module(tut11).
    │ │ │ --export([month_length/2]).
    │ │ │ +since February has 29 days in a leap year.

    -module(tut11).
    │ │ │ +-export([month_length/2]).
    │ │ │  
    │ │ │ -month_length(Year, Month) ->
    │ │ │ +month_length(Year, Month) ->
    │ │ │      %% All years divisible by 400 are leap
    │ │ │      %% Years divisible by 100 are not leap (except the 400 rule above)
    │ │ │      %% Years divisible by 4 are leap (except the 100 rule above)
    │ │ │      Leap = if
    │ │ │ -        trunc(Year / 400) * 400 == Year ->
    │ │ │ +        trunc(Year / 400) * 400 == Year ->
    │ │ │              leap;
    │ │ │ -        trunc(Year / 100) * 100 == Year ->
    │ │ │ +        trunc(Year / 100) * 100 == Year ->
    │ │ │              not_leap;
    │ │ │ -        trunc(Year / 4) * 4 == Year ->
    │ │ │ +        trunc(Year / 4) * 4 == Year ->
    │ │ │              leap;
    │ │ │          true ->
    │ │ │              not_leap
    │ │ │      end,
    │ │ │      case Month of
    │ │ │          sep -> 30;
    │ │ │          apr -> 30;
    │ │ │ @@ -865,152 +865,152 @@
    │ │ │          jan -> 31;
    │ │ │          mar -> 31;
    │ │ │          may -> 31;
    │ │ │          jul -> 31;
    │ │ │          aug -> 31;
    │ │ │          oct -> 31;
    │ │ │          dec -> 31
    │ │ │ -    end.
    70> c(tut11).
    │ │ │ -{ok,tut11}
    │ │ │ -71> tut11:month_length(2004, feb).
    │ │ │ +    end.
    70> c(tut11).
    │ │ │ +{ok,tut11}
    │ │ │ +71> tut11:month_length(2004, feb).
    │ │ │  29
    │ │ │ -72> tut11:month_length(2003, feb).
    │ │ │ +72> tut11:month_length(2003, feb).
    │ │ │  28
    │ │ │ -73> tut11:month_length(1947, aug).
    │ │ │ +73> tut11:month_length(1947, aug).
    │ │ │  31

    │ │ │ │ │ │ │ │ │ │ │ │ Built-In Functions (BIFs) │ │ │

    │ │ │

    BIFs are functions that for some reason are built-in to the Erlang virtual │ │ │ machine. BIFs often implement functionality that is impossible or is too │ │ │ inefficient to implement in Erlang. Some BIFs can be called using the function │ │ │ name only but they are by default belonging to the erlang module. For example, │ │ │ the call to the BIF trunc below is equivalent to a call to erlang:trunc.

    As shown, first it is checked if a year is leap. If a year is divisible by 400, │ │ │ it is a leap year. To determine this, first divide the year by 400 and use the │ │ │ BIF trunc (more about this later) to cut off any decimals. Then multiply by │ │ │ 400 again and see if the same value is returned again. For example, year 2004:

    2004 / 400 = 5.01
    │ │ │ -trunc(5.01) = 5
    │ │ │ +trunc(5.01) = 5
    │ │ │  5 * 400 = 2000

    2000 is not the same as 2004, so 2004 is not divisible by 400. Year 2000:

    2000 / 400 = 5.0
    │ │ │ -trunc(5.0) = 5
    │ │ │ +trunc(5.0) = 5
    │ │ │  5 * 400 = 2000

    That is, a leap year. The next two trunc-tests evaluate if the year is │ │ │ divisible by 100 or 4 in the same way. The first if returns leap or │ │ │ not_leap, which lands up in the variable Leap. This variable is used in the │ │ │ guard for feb in the following case that tells us how long the month is.

    This example showed the use of trunc. It is easier to use the Erlang operator │ │ │ rem that gives the remainder after division, for example:

    74> 2004 rem 400.
    │ │ │ -4

    So instead of writing:

    trunc(Year / 400) * 400 == Year ->
    │ │ │ +4

    So instead of writing:

    trunc(Year / 400) * 400 == Year ->
    │ │ │      leap;

    it can be written:

    Year rem 400 == 0 ->
    │ │ │      leap;

    There are many other BIFs such as trunc. Only a few BIFs can be used in │ │ │ guards, and you cannot use functions you have defined yourself in guards. (see │ │ │ Guard Sequences) (For advanced readers: This is to │ │ │ ensure that guards do not have side effects.) Let us play with a few of these │ │ │ -functions in the shell:

    75> trunc(5.6).
    │ │ │ +functions in the shell:

    75> trunc(5.6).
    │ │ │  5
    │ │ │ -76> round(5.6).
    │ │ │ +76> round(5.6).
    │ │ │  6
    │ │ │ -77> length([a,b,c,d]).
    │ │ │ +77> length([a,b,c,d]).
    │ │ │  4
    │ │ │ -78> float(5).
    │ │ │ +78> float(5).
    │ │ │  5.0
    │ │ │ -79> is_atom(hello).
    │ │ │ +79> is_atom(hello).
    │ │ │  true
    │ │ │ -80> is_atom("hello").
    │ │ │ +80> is_atom("hello").
    │ │ │  false
    │ │ │ -81> is_tuple({paris, {c, 30}}).
    │ │ │ +81> is_tuple({paris, {c, 30}}).
    │ │ │  true
    │ │ │ -82> is_tuple([paris, {c, 30}]).
    │ │ │ +82> is_tuple([paris, {c, 30}]).
    │ │ │  false

    All of these can be used in guards. Now for some BIFs that cannot be used in │ │ │ -guards:

    83> atom_to_list(hello).
    │ │ │ +guards:

    83> atom_to_list(hello).
    │ │ │  "hello"
    │ │ │ -84> list_to_atom("goodbye").
    │ │ │ +84> list_to_atom("goodbye").
    │ │ │  goodbye
    │ │ │ -85> integer_to_list(22).
    │ │ │ +85> integer_to_list(22).
    │ │ │  "22"

    These three BIFs do conversions that would be difficult (or impossible) to do in │ │ │ Erlang.

    │ │ │ │ │ │ │ │ │ │ │ │ Higher-Order Functions (Funs) │ │ │

    │ │ │

    Erlang, like most modern functional programming languages, has higher-order │ │ │ -functions. Here is an example using the shell:

    86> Xf = fun(X) -> X * 2 end.
    │ │ │ +functions. Here is an example using the shell:

    86> Xf = fun(X) -> X * 2 end.
    │ │ │  #Fun<erl_eval.5.123085357>
    │ │ │ -87> Xf(5).
    │ │ │ +87> Xf(5).
    │ │ │  10

    Here is defined a function that doubles the value of a number and assigned this │ │ │ function to a variable. Thus Xf(5) returns value 10. Two useful functions when │ │ │ -working with lists are foreach and map, which are defined as follows:

    foreach(Fun, [First|Rest]) ->
    │ │ │ -    Fun(First),
    │ │ │ -    foreach(Fun, Rest);
    │ │ │ -foreach(Fun, []) ->
    │ │ │ +working with lists are foreach and map, which are defined as follows:

    foreach(Fun, [First|Rest]) ->
    │ │ │ +    Fun(First),
    │ │ │ +    foreach(Fun, Rest);
    │ │ │ +foreach(Fun, []) ->
    │ │ │      ok.
    │ │ │  
    │ │ │ -map(Fun, [First|Rest]) ->
    │ │ │ -    [Fun(First)|map(Fun,Rest)];
    │ │ │ -map(Fun, []) ->
    │ │ │ -    [].

    These two functions are provided in the standard module lists. foreach takes │ │ │ +map(Fun, [First|Rest]) -> │ │ │ + [Fun(First)|map(Fun,Rest)]; │ │ │ +map(Fun, []) -> │ │ │ + [].

    These two functions are provided in the standard module lists. foreach takes │ │ │ a list and applies a fun to every element in the list. map creates a new list │ │ │ by applying a fun to every element in a list. Going back to the shell, map is │ │ │ -used and a fun to add 3 to every element of a list:

    88> Add_3 = fun(X) -> X + 3 end.
    │ │ │ +used and a fun to add 3 to every element of a list:

    88> Add_3 = fun(X) -> X + 3 end.
    │ │ │  #Fun<erl_eval.5.123085357>
    │ │ │ -89> lists:map(Add_3, [1,2,3]).
    │ │ │ -[4,5,6]

    Let us (again) print the temperatures in a list of cities:

    90> Print_City = fun({City, {X, Temp}}) -> io:format("~-15w ~w ~w~n",
    │ │ │ -[City, X, Temp]) end.
    │ │ │ +89> lists:map(Add_3, [1,2,3]).
    │ │ │ +[4,5,6]

    Let us (again) print the temperatures in a list of cities:

    90> Print_City = fun({City, {X, Temp}}) -> io:format("~-15w ~w ~w~n",
    │ │ │ +[City, X, Temp]) end.
    │ │ │  #Fun<erl_eval.5.123085357>
    │ │ │ -91> lists:foreach(Print_City, [{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ -{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │ +91> lists:foreach(Print_City, [{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ +{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │  moscow          c -10
    │ │ │  cape_town       f 70
    │ │ │  stockholm       c -4
    │ │ │  paris           f 28
    │ │ │  london          f 36
    │ │ │  ok

    Let us now define a fun that can be used to go through a list of cities and │ │ │ -temperatures and transform them all to Celsius.

    -module(tut13).
    │ │ │ +temperatures and transform them all to Celsius.

    -module(tut13).
    │ │ │  
    │ │ │ --export([convert_list_to_c/1]).
    │ │ │ +-export([convert_list_to_c/1]).
    │ │ │  
    │ │ │ -convert_to_c({Name, {f, Temp}}) ->
    │ │ │ -    {Name, {c, trunc((Temp - 32) * 5 / 9)}};
    │ │ │ -convert_to_c({Name, {c, Temp}}) ->
    │ │ │ -    {Name, {c, Temp}}.
    │ │ │ -
    │ │ │ -convert_list_to_c(List) ->
    │ │ │ -    lists:map(fun convert_to_c/1, List).
    92> tut13:convert_list_to_c([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ -{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │ -[{moscow,{c,-10}},
    │ │ │ - {cape_town,{c,21}},
    │ │ │ - {stockholm,{c,-4}},
    │ │ │ - {paris,{c,-2}},
    │ │ │ - {london,{c,2}}]

    The convert_to_c function is the same as before, but here it is used as a fun:

    lists:map(fun convert_to_c/1, List)

    When a function defined elsewhere is used as a fun, it can be referred to as │ │ │ +convert_to_c({Name, {f, Temp}}) -> │ │ │ + {Name, {c, trunc((Temp - 32) * 5 / 9)}}; │ │ │ +convert_to_c({Name, {c, Temp}}) -> │ │ │ + {Name, {c, Temp}}. │ │ │ + │ │ │ +convert_list_to_c(List) -> │ │ │ + lists:map(fun convert_to_c/1, List).

    92> tut13:convert_list_to_c([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ +{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │ +[{moscow,{c,-10}},
    │ │ │ + {cape_town,{c,21}},
    │ │ │ + {stockholm,{c,-4}},
    │ │ │ + {paris,{c,-2}},
    │ │ │ + {london,{c,2}}]

    The convert_to_c function is the same as before, but here it is used as a fun:

    lists:map(fun convert_to_c/1, List)

    When a function defined elsewhere is used as a fun, it can be referred to as │ │ │ Function/Arity (remember that Arity = number of arguments). So in the │ │ │ map-call lists:map(fun convert_to_c/1, List) is written. As shown, │ │ │ convert_list_to_c becomes much shorter and easier to understand.

    The standard module lists also contains a function sort(Fun, List) where │ │ │ Fun is a fun with two arguments. This fun returns true if the first argument │ │ │ is less than the second argument, or else false. Sorting is added to the │ │ │ -convert_list_to_c:

    -module(tut13).
    │ │ │ +convert_list_to_c:

    -module(tut13).
    │ │ │  
    │ │ │ --export([convert_list_to_c/1]).
    │ │ │ +-export([convert_list_to_c/1]).
    │ │ │  
    │ │ │ -convert_to_c({Name, {f, Temp}}) ->
    │ │ │ -    {Name, {c, trunc((Temp - 32) * 5 / 9)}};
    │ │ │ -convert_to_c({Name, {c, Temp}}) ->
    │ │ │ -    {Name, {c, Temp}}.
    │ │ │ -
    │ │ │ -convert_list_to_c(List) ->
    │ │ │ -    New_list = lists:map(fun convert_to_c/1, List),
    │ │ │ -    lists:sort(fun({_, {c, Temp1}}, {_, {c, Temp2}}) ->
    │ │ │ -                       Temp1 < Temp2 end, New_list).
    93> c(tut13).
    │ │ │ -{ok,tut13}
    │ │ │ -94> tut13:convert_list_to_c([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ -{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │ -[{moscow,{c,-10}},
    │ │ │ - {stockholm,{c,-4}},
    │ │ │ - {paris,{c,-2}},
    │ │ │ - {london,{c,2}},
    │ │ │ - {cape_town,{c,21}}]

    In sort the fun is used:

    fun({_, {c, Temp1}}, {_, {c, Temp2}}) -> Temp1 < Temp2 end,

    Here the concept of an anonymous variable _ is introduced. This is simply │ │ │ +convert_to_c({Name, {f, Temp}}) -> │ │ │ + {Name, {c, trunc((Temp - 32) * 5 / 9)}}; │ │ │ +convert_to_c({Name, {c, Temp}}) -> │ │ │ + {Name, {c, Temp}}. │ │ │ + │ │ │ +convert_list_to_c(List) -> │ │ │ + New_list = lists:map(fun convert_to_c/1, List), │ │ │ + lists:sort(fun({_, {c, Temp1}}, {_, {c, Temp2}}) -> │ │ │ + Temp1 < Temp2 end, New_list).

    93> c(tut13).
    │ │ │ +{ok,tut13}
    │ │ │ +94> tut13:convert_list_to_c([{moscow, {c, -10}}, {cape_town, {f, 70}},
    │ │ │ +{stockholm, {c, -4}}, {paris, {f, 28}}, {london, {f, 36}}]).
    │ │ │ +[{moscow,{c,-10}},
    │ │ │ + {stockholm,{c,-4}},
    │ │ │ + {paris,{c,-2}},
    │ │ │ + {london,{c,2}},
    │ │ │ + {cape_town,{c,21}}]

    In sort the fun is used:

    fun({_, {c, Temp1}}, {_, {c, Temp2}}) -> Temp1 < Temp2 end,

    Here the concept of an anonymous variable _ is introduced. This is simply │ │ │ shorthand for a variable that gets a value, but the value is ignored. This can │ │ │ be used anywhere suitable, not just in funs. Temp1 < Temp2 returns true if │ │ │ Temp1 is less than Temp2.

    │ │ │
    │ │ │ │ │ │
    │ │ │
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/spec_proc.html │ │ │ @@ -123,72 +123,72 @@ │ │ │ │ │ │ │ │ │ │ │ │ Simple Debugging │ │ │ │ │ │

    The sys module has functions for simple debugging of processes implemented │ │ │ using behaviours. The code_lock example from │ │ │ -gen_statem Behaviour is used to illustrate this:

    Erlang/OTP 27 [erts-15.0] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
    │ │ │ +gen_statem Behaviour is used to illustrate this:

    Erlang/OTP 27 [erts-15.0] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
    │ │ │  
    │ │ │ -Eshell V15.0 (press Ctrl+G to abort, type help(). for help)
    │ │ │ -1> code_lock:start_link([1,2,3,4]).
    │ │ │ +Eshell V15.0 (press Ctrl+G to abort, type help(). for help)
    │ │ │ +1> code_lock:start_link([1,2,3,4]).
    │ │ │  Lock
    │ │ │ -{ok,<0.90.0>}
    │ │ │ -2> sys:statistics(code_lock, true).
    │ │ │ +{ok,<0.90.0>}
    │ │ │ +2> sys:statistics(code_lock, true).
    │ │ │  ok
    │ │ │ -3> sys:trace(code_lock, true).
    │ │ │ +3> sys:trace(code_lock, true).
    │ │ │  ok
    │ │ │ -4> code_lock:button(1).
    │ │ │ -*DBG* code_lock receive cast {button,1} in state locked
    │ │ │ +4> code_lock:button(1).
    │ │ │ +*DBG* code_lock receive cast {button,1} in state locked
    │ │ │  ok
    │ │ │ -*DBG* code_lock consume cast {button,1} in state locked
    │ │ │ -5> code_lock:button(2).
    │ │ │ -*DBG* code_lock receive cast {button,2} in state locked
    │ │ │ +*DBG* code_lock consume cast {button,1} in state locked
    │ │ │ +5> code_lock:button(2).
    │ │ │ +*DBG* code_lock receive cast {button,2} in state locked
    │ │ │  ok
    │ │ │ -*DBG* code_lock consume cast {button,2} in state locked
    │ │ │ -6> code_lock:button(3).
    │ │ │ -*DBG* code_lock receive cast {button,3} in state locked
    │ │ │ +*DBG* code_lock consume cast {button,2} in state locked
    │ │ │ +6> code_lock:button(3).
    │ │ │ +*DBG* code_lock receive cast {button,3} in state locked
    │ │ │  ok
    │ │ │ -*DBG* code_lock consume cast {button,3} in state locked
    │ │ │ -7> code_lock:button(4).
    │ │ │ -*DBG* code_lock receive cast {button,4} in state locked
    │ │ │ +*DBG* code_lock consume cast {button,3} in state locked
    │ │ │ +7> code_lock:button(4).
    │ │ │ +*DBG* code_lock receive cast {button,4} in state locked
    │ │ │  ok
    │ │ │  Unlock
    │ │ │ -*DBG* code_lock consume cast {button,4} in state locked => open
    │ │ │ -*DBG* code_lock start_timer {state_timeout,10000,lock,[]} in state open
    │ │ │ +*DBG* code_lock consume cast {button,4} in state locked => open
    │ │ │ +*DBG* code_lock start_timer {state_timeout,10000,lock,[]} in state open
    │ │ │  *DBG* code_lock receive state_timeout lock in state open
    │ │ │  Lock
    │ │ │  *DBG* code_lock consume state_timeout lock in state open => locked
    │ │ │ -8> sys:statistics(code_lock, get).
    │ │ │ -{ok,[{start_time,{{2024,5,3},{8,11,1}}},
    │ │ │ -     {current_time,{{2024,5,3},{8,11,48}}},
    │ │ │ -     {reductions,4098},
    │ │ │ -     {messages_in,5},
    │ │ │ -     {messages_out,0}]}
    │ │ │ -9> sys:statistics(code_lock, false).
    │ │ │ -ok
    │ │ │ -10> sys:trace(code_lock, false).
    │ │ │ -ok
    │ │ │ -11> sys:get_status(code_lock).
    │ │ │ -{status,<0.90.0>,
    │ │ │ -        {module,gen_statem},
    │ │ │ -        [[{'$initial_call',{code_lock,init,1}},
    │ │ │ -          {'$ancestors',[<0.88.0>,<0.87.0>,<0.70.0>,<0.65.0>,<0.69.0>,
    │ │ │ -                         <0.64.0>,kernel_sup,<0.47.0>]}],
    │ │ │ -         running,<0.88.0>,[],
    │ │ │ -         [{header,"Status for state machine code_lock"},
    │ │ │ -          {data,[{"Status",running},
    │ │ │ -                 {"Parent",<0.88.0>},
    │ │ │ -                 {"Modules",[code_lock]},
    │ │ │ -                 {"Time-outs",{0,[]}},
    │ │ │ -                 {"Logged Events",[]},
    │ │ │ -                 {"Postponed",[]}]},
    │ │ │ -          {data,[{"State",
    │ │ │ -                  {locked,#{code => [1,2,3,4],
    │ │ │ -                            length => 4,buttons => []}}}]}]]}

    │ │ │ +8> sys:statistics(code_lock, get). │ │ │ +{ok,[{start_time,{{2024,5,3},{8,11,1}}}, │ │ │ + {current_time,{{2024,5,3},{8,11,48}}}, │ │ │ + {reductions,4098}, │ │ │ + {messages_in,5}, │ │ │ + {messages_out,0}]} │ │ │ +9> sys:statistics(code_lock, false). │ │ │ +ok │ │ │ +10> sys:trace(code_lock, false). │ │ │ +ok │ │ │ +11> sys:get_status(code_lock). │ │ │ +{status,<0.90.0>, │ │ │ + {module,gen_statem}, │ │ │ + [[{'$initial_call',{code_lock,init,1}}, │ │ │ + {'$ancestors',[<0.88.0>,<0.87.0>,<0.70.0>,<0.65.0>,<0.69.0>, │ │ │ + <0.64.0>,kernel_sup,<0.47.0>]}], │ │ │ + running,<0.88.0>,[], │ │ │ + [{header,"Status for state machine code_lock"}, │ │ │ + {data,[{"Status",running}, │ │ │ + {"Parent",<0.88.0>}, │ │ │ + {"Modules",[code_lock]}, │ │ │ + {"Time-outs",{0,[]}}, │ │ │ + {"Logged Events",[]}, │ │ │ + {"Postponed",[]}]}, │ │ │ + {data,[{"State", │ │ │ + {locked,#{code => [1,2,3,4], │ │ │ + length => 4,buttons => []}}}]}]]}

    │ │ │ │ │ │ │ │ │ │ │ │ Special Processes │ │ │

    │ │ │

    This section describes how to write a process that complies to the OTP design │ │ │ principles, without using a standard behaviour. Such a process is to:

    System messages are messages with a special meaning, used in the supervision │ │ │ @@ -198,238 +198,238 @@ │ │ │ │ │ │ │ │ │ │ │ │ Example │ │ │ │ │ │

    Here follows the simple server from │ │ │ Overview, │ │ │ -implemented using sys and proc_lib to fit into a supervision tree:

    -module(ch4).
    │ │ │ --export([start_link/0]).
    │ │ │ --export([alloc/0, free/1]).
    │ │ │ --export([init/1]).
    │ │ │ --export([system_continue/3, system_terminate/4,
    │ │ │ +implemented using sys and proc_lib to fit into a supervision tree:

    -module(ch4).
    │ │ │ +-export([start_link/0]).
    │ │ │ +-export([alloc/0, free/1]).
    │ │ │ +-export([init/1]).
    │ │ │ +-export([system_continue/3, system_terminate/4,
    │ │ │           write_debug/3,
    │ │ │ -         system_get_state/1, system_replace_state/2]).
    │ │ │ +         system_get_state/1, system_replace_state/2]).
    │ │ │  
    │ │ │ -start_link() ->
    │ │ │ -    proc_lib:start_link(ch4, init, [self()]).
    │ │ │ +start_link() ->
    │ │ │ +    proc_lib:start_link(ch4, init, [self()]).
    │ │ │  
    │ │ │ -alloc() ->
    │ │ │ -    ch4 ! {self(), alloc},
    │ │ │ +alloc() ->
    │ │ │ +    ch4 ! {self(), alloc},
    │ │ │      receive
    │ │ │ -        {ch4, Res} ->
    │ │ │ +        {ch4, Res} ->
    │ │ │              Res
    │ │ │      end.
    │ │ │  
    │ │ │ -free(Ch) ->
    │ │ │ -    ch4 ! {free, Ch},
    │ │ │ +free(Ch) ->
    │ │ │ +    ch4 ! {free, Ch},
    │ │ │      ok.
    │ │ │  
    │ │ │ -init(Parent) ->
    │ │ │ -    register(ch4, self()),
    │ │ │ -    Chs = channels(),
    │ │ │ -    Deb = sys:debug_options([]),
    │ │ │ -    proc_lib:init_ack(Parent, {ok, self()}),
    │ │ │ -    loop(Chs, Parent, Deb).
    │ │ │ +init(Parent) ->
    │ │ │ +    register(ch4, self()),
    │ │ │ +    Chs = channels(),
    │ │ │ +    Deb = sys:debug_options([]),
    │ │ │ +    proc_lib:init_ack(Parent, {ok, self()}),
    │ │ │ +    loop(Chs, Parent, Deb).
    │ │ │  
    │ │ │ -loop(Chs, Parent, Deb) ->
    │ │ │ +loop(Chs, Parent, Deb) ->
    │ │ │      receive
    │ │ │ -        {From, alloc} ->
    │ │ │ -            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
    │ │ │ -                                    ch4, {in, alloc, From}),
    │ │ │ -            {Ch, Chs2} = alloc(Chs),
    │ │ │ -            From ! {ch4, Ch},
    │ │ │ -            Deb3 = sys:handle_debug(Deb2, fun ch4:write_debug/3,
    │ │ │ -                                    ch4, {out, {ch4, Ch}, From}),
    │ │ │ -            loop(Chs2, Parent, Deb3);
    │ │ │ -        {free, Ch} ->
    │ │ │ -            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
    │ │ │ -                                    ch4, {in, {free, Ch}}),
    │ │ │ -            Chs2 = free(Ch, Chs),
    │ │ │ -            loop(Chs2, Parent, Deb2);
    │ │ │ -
    │ │ │ -        {system, From, Request} ->
    │ │ │ -            sys:handle_system_msg(Request, From, Parent,
    │ │ │ -                                  ch4, Deb, Chs)
    │ │ │ +        {From, alloc} ->
    │ │ │ +            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
    │ │ │ +                                    ch4, {in, alloc, From}),
    │ │ │ +            {Ch, Chs2} = alloc(Chs),
    │ │ │ +            From ! {ch4, Ch},
    │ │ │ +            Deb3 = sys:handle_debug(Deb2, fun ch4:write_debug/3,
    │ │ │ +                                    ch4, {out, {ch4, Ch}, From}),
    │ │ │ +            loop(Chs2, Parent, Deb3);
    │ │ │ +        {free, Ch} ->
    │ │ │ +            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
    │ │ │ +                                    ch4, {in, {free, Ch}}),
    │ │ │ +            Chs2 = free(Ch, Chs),
    │ │ │ +            loop(Chs2, Parent, Deb2);
    │ │ │ +
    │ │ │ +        {system, From, Request} ->
    │ │ │ +            sys:handle_system_msg(Request, From, Parent,
    │ │ │ +                                  ch4, Deb, Chs)
    │ │ │      end.
    │ │ │  
    │ │ │ -system_continue(Parent, Deb, Chs) ->
    │ │ │ -    loop(Chs, Parent, Deb).
    │ │ │ +system_continue(Parent, Deb, Chs) ->
    │ │ │ +    loop(Chs, Parent, Deb).
    │ │ │  
    │ │ │ -system_terminate(Reason, _Parent, _Deb, _Chs) ->
    │ │ │ -    exit(Reason).
    │ │ │ +system_terminate(Reason, _Parent, _Deb, _Chs) ->
    │ │ │ +    exit(Reason).
    │ │ │  
    │ │ │ -system_get_state(Chs) ->
    │ │ │ -    {ok, Chs}.
    │ │ │ +system_get_state(Chs) ->
    │ │ │ +    {ok, Chs}.
    │ │ │  
    │ │ │ -system_replace_state(StateFun, Chs) ->
    │ │ │ -    NChs = StateFun(Chs),
    │ │ │ -    {ok, NChs, NChs}.
    │ │ │ +system_replace_state(StateFun, Chs) ->
    │ │ │ +    NChs = StateFun(Chs),
    │ │ │ +    {ok, NChs, NChs}.
    │ │ │  
    │ │ │ -write_debug(Dev, Event, Name) ->
    │ │ │ -    io:format(Dev, "~p event = ~p~n", [Name, Event]).

    As it is not relevant to the example, the channel handling functions have been │ │ │ +write_debug(Dev, Event, Name) -> │ │ │ + io:format(Dev, "~p event = ~p~n", [Name, Event]).

    As it is not relevant to the example, the channel handling functions have been │ │ │ omitted. To compile this example, the │ │ │ implementation of channel handling │ │ │ needs to be added to the module.

    Here is an example showing how the debugging functions in the sys │ │ │ module can be used for ch4:

    % erl
    │ │ │ -Erlang/OTP 27 [erts-15.0] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
    │ │ │ +Erlang/OTP 27 [erts-15.0] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
    │ │ │  
    │ │ │ -Eshell V15.0 (press Ctrl+G to abort, type help(). for help)
    │ │ │ -1> ch4:start_link().
    │ │ │ -{ok,<0.90.0>}
    │ │ │ -2> sys:statistics(ch4, true).
    │ │ │ -ok
    │ │ │ -3> sys:trace(ch4, true).
    │ │ │ -ok
    │ │ │ -4> ch4:alloc().
    │ │ │ -ch4 event = {in,alloc,<0.88.0>}
    │ │ │ -ch4 event = {out,{ch4,1},<0.88.0>}
    │ │ │ +Eshell V15.0 (press Ctrl+G to abort, type help(). for help)
    │ │ │ +1> ch4:start_link().
    │ │ │ +{ok,<0.90.0>}
    │ │ │ +2> sys:statistics(ch4, true).
    │ │ │ +ok
    │ │ │ +3> sys:trace(ch4, true).
    │ │ │ +ok
    │ │ │ +4> ch4:alloc().
    │ │ │ +ch4 event = {in,alloc,<0.88.0>}
    │ │ │ +ch4 event = {out,{ch4,1},<0.88.0>}
    │ │ │  1
    │ │ │ -5> ch4:free(ch1).
    │ │ │ -ch4 event = {in,{free,ch1}}
    │ │ │ +5> ch4:free(ch1).
    │ │ │ +ch4 event = {in,{free,ch1}}
    │ │ │  ok
    │ │ │ -6> sys:statistics(ch4, get).
    │ │ │ -{ok,[{start_time,{{2024,5,3},{8,26,13}}},
    │ │ │ -     {current_time,{{2024,5,3},{8,26,49}}},
    │ │ │ -     {reductions,202},
    │ │ │ -     {messages_in,2},
    │ │ │ -     {messages_out,1}]}
    │ │ │ -7> sys:statistics(ch4, false).
    │ │ │ -ok
    │ │ │ -8> sys:trace(ch4, false).
    │ │ │ -ok
    │ │ │ -9> sys:get_status(ch4).
    │ │ │ -{status,<0.90.0>,
    │ │ │ -        {module,ch4},
    │ │ │ -        [[{'$initial_call',{ch4,init,1}},
    │ │ │ -          {'$ancestors',[<0.88.0>,<0.87.0>,<0.70.0>,<0.65.0>,<0.69.0>,
    │ │ │ -                         <0.64.0>,kernel_sup,<0.47.0>]}],
    │ │ │ -         running,<0.88.0>,[],
    │ │ │ -         {[1],[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19|...]}]}

    │ │ │ +6> sys:statistics(ch4, get). │ │ │ +{ok,[{start_time,{{2024,5,3},{8,26,13}}}, │ │ │ + {current_time,{{2024,5,3},{8,26,49}}}, │ │ │ + {reductions,202}, │ │ │ + {messages_in,2}, │ │ │ + {messages_out,1}]} │ │ │ +7> sys:statistics(ch4, false). │ │ │ +ok │ │ │ +8> sys:trace(ch4, false). │ │ │ +ok │ │ │ +9> sys:get_status(ch4). │ │ │ +{status,<0.90.0>, │ │ │ + {module,ch4}, │ │ │ + [[{'$initial_call',{ch4,init,1}}, │ │ │ + {'$ancestors',[<0.88.0>,<0.87.0>,<0.70.0>,<0.65.0>,<0.69.0>, │ │ │ + <0.64.0>,kernel_sup,<0.47.0>]}], │ │ │ + running,<0.88.0>,[], │ │ │ + {[1],[2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19|...]}]}

    │ │ │ │ │ │ │ │ │ │ │ │ Starting the Process │ │ │

    │ │ │

    A function in the proc_lib module is to be used to start the process. Several │ │ │ functions are available, for example, │ │ │ proc_lib:spawn_link/3,4 │ │ │ for asynchronous start and │ │ │ proc_lib:start_link/3,4,5 for synchronous start.

    Information necessary for a process within a supervision tree, such as │ │ │ details on ancestors and the initial call, is stored when a process │ │ │ is started through one of these functions.

    If the process terminates with a reason other than normal or shutdown, a │ │ │ crash report is generated. For more information about the crash report, see │ │ │ Logging in Kernel User's Guide.

    In the example, synchronous start is used. The process starts by calling │ │ │ -ch4:start_link():

    start_link() ->
    │ │ │ -    proc_lib:start_link(ch4, init, [self()]).

    ch4:start_link/0 calls proc_lib:start_link/3, which takes a module │ │ │ +ch4:start_link():

    start_link() ->
    │ │ │ +    proc_lib:start_link(ch4, init, [self()]).

    ch4:start_link/0 calls proc_lib:start_link/3, which takes a module │ │ │ name, a function name, and an argument list as arguments. It then │ │ │ spawns a new process and establishes a link. The new process starts │ │ │ by executing the given function, here ch4:init(Pid), where Pid is │ │ │ the pid of the parent process (obtained by the call to │ │ │ self() in the call to proc_lib:start_link/3).

    All initialization, including name registration, is done in init/1. The new │ │ │ -process has to acknowledge that it has been started to the parent:

    init(Parent) ->
    │ │ │ +process has to acknowledge that it has been started to the parent:

    init(Parent) ->
    │ │ │      ...
    │ │ │ -    proc_lib:init_ack(Parent, {ok, self()}),
    │ │ │ -    loop(...).

    proc_lib:start_link/3 is synchronous and does not return until │ │ │ + proc_lib:init_ack(Parent, {ok, self()}), │ │ │ + loop(...).

    proc_lib:start_link/3 is synchronous and does not return until │ │ │ proc_lib:init_ack/1,2 or │ │ │ proc_lib:init_fail/2,3 has been called, │ │ │ or the process has exited.

    │ │ │ │ │ │ │ │ │ │ │ │ Debugging │ │ │

    │ │ │

    To support the debug facilities in sys, a debug structure is needed. The │ │ │ -Deb term is initialized using sys:debug_options/1:

    init(Parent) ->
    │ │ │ +Deb term is initialized using sys:debug_options/1:

    init(Parent) ->
    │ │ │      ...
    │ │ │ -    Deb = sys:debug_options([]),
    │ │ │ +    Deb = sys:debug_options([]),
    │ │ │      ...
    │ │ │ -    loop(Chs, Parent, Deb).

    sys:debug_options/1 takes a list of options. Given an empty list as in this │ │ │ + loop(Chs, Parent, Deb).

    sys:debug_options/1 takes a list of options. Given an empty list as in this │ │ │ example means that debugging is initially disabled. For information about the │ │ │ possible options, see sys in STDLIB.

    For each system event to be logged or traced, the following function │ │ │ -is to be called:

    sys:handle_debug(Deb, Func, Info, Event) => Deb1

    The arguments have the follow meaning:

    • Deb is the debug structure as returned from sys:debug_options/1.
    • Func is a fun specifying a (user-defined) function used to format trace │ │ │ +is to be called:

      sys:handle_debug(Deb, Func, Info, Event) => Deb1

      The arguments have the follow meaning:

      • Deb is the debug structure as returned from sys:debug_options/1.
      • Func is a fun specifying a (user-defined) function used to format trace │ │ │ output. For each system event, the format function is called as │ │ │ Func(Dev, Event, Info), where:
        • Dev is the I/O device to which the output is to be printed. See io │ │ │ in STDLIB.
        • Event and Info are passed as-is from the call to sys:handle_debug/4.
      • Info is used to pass more information to Func. It can be any term, and it │ │ │ is passed as-is.
      • Event is the system event. It is up to the user to define what a system │ │ │ event is and how it is to be represented. Typically, at least incoming and │ │ │ outgoing messages are considered system events and represented by the tuples │ │ │ {in,Msg[,From]} and {out,Msg,To[,State]}, respectively.

      sys:handle_debug/4 returns an updated debug structure Deb1.

      In the example, sys:handle_debug/4 is called for each incoming and │ │ │ outgoing message. The format function Func is the function │ │ │ -ch4:write_debug/3, which prints the message using io:format/3.

      loop(Chs, Parent, Deb) ->
      │ │ │ +ch4:write_debug/3, which prints the message using io:format/3.

      loop(Chs, Parent, Deb) ->
      │ │ │      receive
      │ │ │ -        {From, alloc} ->
      │ │ │ -            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
      │ │ │ -                                    ch4, {in, alloc, From}),
      │ │ │ -            {Ch, Chs2} = alloc(Chs),
      │ │ │ -            From ! {ch4, Ch},
      │ │ │ -            Deb3 = sys:handle_debug(Deb2, fun ch4:write_debug/3,
      │ │ │ -                                    ch4, {out, {ch4, Ch}, From}),
      │ │ │ -            loop(Chs2, Parent, Deb3);
      │ │ │ -        {free, Ch} ->
      │ │ │ -            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
      │ │ │ -                                    ch4, {in, {free, Ch}}),
      │ │ │ -            Chs2 = free(Ch, Chs),
      │ │ │ -            loop(Chs2, Parent, Deb2);
      │ │ │ +        {From, alloc} ->
      │ │ │ +            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
      │ │ │ +                                    ch4, {in, alloc, From}),
      │ │ │ +            {Ch, Chs2} = alloc(Chs),
      │ │ │ +            From ! {ch4, Ch},
      │ │ │ +            Deb3 = sys:handle_debug(Deb2, fun ch4:write_debug/3,
      │ │ │ +                                    ch4, {out, {ch4, Ch}, From}),
      │ │ │ +            loop(Chs2, Parent, Deb3);
      │ │ │ +        {free, Ch} ->
      │ │ │ +            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
      │ │ │ +                                    ch4, {in, {free, Ch}}),
      │ │ │ +            Chs2 = free(Ch, Chs),
      │ │ │ +            loop(Chs2, Parent, Deb2);
      │ │ │          ...
      │ │ │      end.
      │ │ │  
      │ │ │ -write_debug(Dev, Event, Name) ->
      │ │ │ -    io:format(Dev, "~p event = ~p~n", [Name, Event]).

      │ │ │ +write_debug(Dev, Event, Name) -> │ │ │ + io:format(Dev, "~p event = ~p~n", [Name, Event]).

      │ │ │ │ │ │ │ │ │ │ │ │ Handling System Messages │ │ │

      │ │ │

      System messages are received as:

      {system, From, Request}

      The content and meaning of these messages are not to be interpreted by the │ │ │ -process. Instead the following function is to be called:

      sys:handle_system_msg(Request, From, Parent, Module, Deb, State)

      The arguments have the following meaning:

      • Request and From from the received system message are to be │ │ │ +process. Instead the following function is to be called:

        sys:handle_system_msg(Request, From, Parent, Module, Deb, State)

        The arguments have the following meaning:

        • Request and From from the received system message are to be │ │ │ passed as-is to the call to sys:handle_system_msg/6.
        • Parent is the pid of the parent process.
        • Module is the name of the module implementing the speciall process.
        • Deb is the debug structure.
        • State is a term describing the internal state and is passed on to │ │ │ Module:system_continue/3, Module:system_terminate/4/ │ │ │ Module:system_get_state/1, and Module:system_replace_state/2.

        sys:handle_system_msg/6 does not return. It handles the system │ │ │ message and eventually calls either of the following functions:

        • Module:system_continue(Parent, Deb, State) - if process execution is to │ │ │ continue.

        • Module:system_terminate(Reason, Parent, Deb, State) - if the │ │ │ process is to terminate.

        While handling the system message, sys:handle_system_msg/6 can call │ │ │ one of the following functions:

        • Module:system_get_state(State) - if the process is to return its state.

        • Module:system_replace_state(StateFun, State) - if the process is │ │ │ to replace its state using the fun StateFun fun. See sys:replace_state/3 │ │ │ for more information.

        • system_code_change(Misc, Module, OldVsn, Extra) - if the process is to │ │ │ perform a code change.

        A process in a supervision tree is expected to terminate with the same reason as │ │ │ -its parent.

        In the example, system messages are handed by the following code:

        loop(Chs, Parent, Deb) ->
        │ │ │ +its parent.

        In the example, system messages are handed by the following code:

        loop(Chs, Parent, Deb) ->
        │ │ │      receive
        │ │ │          ...
        │ │ │  
        │ │ │ -        {system, From, Request} ->
        │ │ │ -            sys:handle_system_msg(Request, From, Parent,
        │ │ │ -                                  ch4, Deb, Chs)
        │ │ │ +        {system, From, Request} ->
        │ │ │ +            sys:handle_system_msg(Request, From, Parent,
        │ │ │ +                                  ch4, Deb, Chs)
        │ │ │      end.
        │ │ │  
        │ │ │ -system_continue(Parent, Deb, Chs) ->
        │ │ │ -    loop(Chs, Parent, Deb).
        │ │ │ +system_continue(Parent, Deb, Chs) ->
        │ │ │ +    loop(Chs, Parent, Deb).
        │ │ │  
        │ │ │ -system_terminate(Reason, Parent, Deb, Chs) ->
        │ │ │ -    exit(Reason).
        │ │ │ +system_terminate(Reason, Parent, Deb, Chs) ->
        │ │ │ +    exit(Reason).
        │ │ │  
        │ │ │ -system_get_state(Chs) ->
        │ │ │ -    {ok, Chs, Chs}.
        │ │ │ +system_get_state(Chs) ->
        │ │ │ +    {ok, Chs, Chs}.
        │ │ │  
        │ │ │ -system_replace_state(StateFun, Chs) ->
        │ │ │ -    NChs = StateFun(Chs),
        │ │ │ -    {ok, NChs, NChs}.

        If a special process is configured to trap exits, it must take notice │ │ │ +system_replace_state(StateFun, Chs) -> │ │ │ + NChs = StateFun(Chs), │ │ │ + {ok, NChs, NChs}.

        If a special process is configured to trap exits, it must take notice │ │ │ of 'EXIT' messages from its parent process and terminate using the │ │ │ -same exit reason once the parent process has terminated.

        Here is an example:

        init(Parent) ->
        │ │ │ +same exit reason once the parent process has terminated.

        Here is an example:

        init(Parent) ->
        │ │ │      ...,
        │ │ │ -    process_flag(trap_exit, true),
        │ │ │ +    process_flag(trap_exit, true),
        │ │ │      ...,
        │ │ │ -    loop(Parent).
        │ │ │ +    loop(Parent).
        │ │ │  
        │ │ │ -loop(Parent) ->
        │ │ │ +loop(Parent) ->
        │ │ │      receive
        │ │ │          ...
        │ │ │ -        {'EXIT', Parent, Reason} ->
        │ │ │ +        {'EXIT', Parent, Reason} ->
        │ │ │              %% Clean up here, if needed.
        │ │ │ -            exit(Reason);
        │ │ │ +            exit(Reason);
        │ │ │          ...
        │ │ │      end.

        │ │ │ │ │ │ │ │ │ │ │ │ User-Defined Behaviours │ │ │

        │ │ │ @@ -448,71 +448,71 @@ │ │ │ function. Note that the -optional_callbacks attribute is to be used together │ │ │ with the -callback attribute; it cannot be combined with the │ │ │ behaviour_info() function described below.

        Tools that need to know about optional callback functions can call │ │ │ Behaviour:behaviour_info(optional_callbacks) to get a list of all optional │ │ │ callback functions.

        Note

        We recommend using the -callback attribute rather than the │ │ │ behaviour_info() function. The reason is that the extra type information can │ │ │ be used by tools to produce documentation or find discrepancies.

        As an alternative to the -callback and -optional_callbacks attributes you │ │ │ -may directly implement and export behaviour_info():

        behaviour_info(callbacks) ->
        │ │ │ -    [{Name1, Arity1},...,{NameN, ArityN}].

        where each {Name, Arity} specifies the name and arity of a callback function. │ │ │ +may directly implement and export behaviour_info():

        behaviour_info(callbacks) ->
        │ │ │ +    [{Name1, Arity1},...,{NameN, ArityN}].

        where each {Name, Arity} specifies the name and arity of a callback function. │ │ │ This function is otherwise automatically generated by the compiler using the │ │ │ -callback attributes.

        When the compiler encounters the module attribute -behaviour(Behaviour). in a │ │ │ module Mod, it calls Behaviour:behaviour_info(callbacks) and compares the │ │ │ result with the set of functions actually exported from Mod, and issues a │ │ │ warning if any callback function is missing.

        Example:

        %% User-defined behaviour module
        │ │ │ --module(simple_server).
        │ │ │ --export([start_link/2, init/3, ...]).
        │ │ │ +-module(simple_server).
        │ │ │ +-export([start_link/2, init/3, ...]).
        │ │ │  
        │ │ │ --callback init(State :: term()) -> 'ok'.
        │ │ │ --callback handle_req(Req :: term(), State :: term()) -> {'ok', Reply :: term()}.
        │ │ │ --callback terminate() -> 'ok'.
        │ │ │ --callback format_state(State :: term()) -> term().
        │ │ │ +-callback init(State :: term()) -> 'ok'.
        │ │ │ +-callback handle_req(Req :: term(), State :: term()) -> {'ok', Reply :: term()}.
        │ │ │ +-callback terminate() -> 'ok'.
        │ │ │ +-callback format_state(State :: term()) -> term().
        │ │ │  
        │ │ │ --optional_callbacks([format_state/1]).
        │ │ │ +-optional_callbacks([format_state/1]).
        │ │ │  
        │ │ │  %% Alternatively you may define:
        │ │ │  %%
        │ │ │  %% -export([behaviour_info/1]).
        │ │ │  %% behaviour_info(callbacks) ->
        │ │ │  %%     [{init,1},
        │ │ │  %%      {handle_req,2},
        │ │ │  %%      {terminate,0}].
        │ │ │  
        │ │ │ -start_link(Name, Module) ->
        │ │ │ -    proc_lib:start_link(?MODULE, init, [self(), Name, Module]).
        │ │ │ +start_link(Name, Module) ->
        │ │ │ +    proc_lib:start_link(?MODULE, init, [self(), Name, Module]).
        │ │ │  
        │ │ │ -init(Parent, Name, Module) ->
        │ │ │ -    register(Name, self()),
        │ │ │ +init(Parent, Name, Module) ->
        │ │ │ +    register(Name, self()),
        │ │ │      ...,
        │ │ │ -    Dbg = sys:debug_options([]),
        │ │ │ -    proc_lib:init_ack(Parent, {ok, self()}),
        │ │ │ -    loop(Parent, Module, Deb, ...).
        │ │ │ +    Dbg = sys:debug_options([]),
        │ │ │ +    proc_lib:init_ack(Parent, {ok, self()}),
        │ │ │ +    loop(Parent, Module, Deb, ...).
        │ │ │  
        │ │ │ -...

        In a callback module:

        -module(db).
        │ │ │ --behaviour(simple_server).
        │ │ │ +...

        In a callback module:

        -module(db).
        │ │ │ +-behaviour(simple_server).
        │ │ │  
        │ │ │ --export([init/1, handle_req/2, terminate/0]).
        │ │ │ +-export([init/1, handle_req/2, terminate/0]).
        │ │ │  
        │ │ │  ...

        The contracts specified with -callback attributes in behaviour modules can be │ │ │ further refined by adding -spec attributes in callback modules. This can be │ │ │ useful as -callback contracts are usually generic. The same callback module │ │ │ -with contracts for the callbacks:

        -module(db).
        │ │ │ --behaviour(simple_server).
        │ │ │ +with contracts for the callbacks:

        -module(db).
        │ │ │ +-behaviour(simple_server).
        │ │ │  
        │ │ │ --export([init/1, handle_req/2, terminate/0]).
        │ │ │ +-export([init/1, handle_req/2, terminate/0]).
        │ │ │  
        │ │ │ --record(state, {field1 :: [atom()], field2 :: integer()}).
        │ │ │ +-record(state, {field1 :: [atom()], field2 :: integer()}).
        │ │ │  
        │ │ │ --type state()   :: #state{}.
        │ │ │ --type request() :: {'store', term(), term()};
        │ │ │ -                   {'lookup', term()}.
        │ │ │ +-type state()   :: #state{}.
        │ │ │ +-type request() :: {'store', term(), term()};
        │ │ │ +                   {'lookup', term()}.
        │ │ │  
        │ │ │  ...
        │ │ │  
        │ │ │ --spec handle_req(request(), state()) -> {'ok', term()}.
        │ │ │ +-spec handle_req(request(), state()) -> {'ok', term()}.
        │ │ │  
        │ │ │  ...

        Each -spec contract is to be a subtype of the respective -callback contract.

        │ │ │ │ │ │ │ │ │
        │ │ │
        │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/statem.html │ │ │ @@ -124,15 +124,15 @@ │ │ │ │ │ │

        Established Automata Theory does not deal much with how a state transition │ │ │ is triggered, but assumes that the output is a function of the input │ │ │ (and the state) and that they are some kind of values.

        For an Event-Driven State Machine, the input is an event that triggers │ │ │ a state transition and the output is actions executed during │ │ │ the state transition. Analogously to the mathematical model │ │ │ of a Finite State Machine, it can be described as a set of relations │ │ │ -of the following form:

        State(S) x Event(E) -> Actions(A), State(S')

        These relations are interpreted as follows: if we are in state S, │ │ │ +of the following form:

        State(S) x Event(E) -> Actions(A), State(S')

        These relations are interpreted as follows: if we are in state S, │ │ │ and event E occurs, we are to perform actions A, and make a transition │ │ │ to state S'. Notice that S' can be equal to S, │ │ │ and that A can be empty.

        In gen_statem we define a state change as a state transition in which the │ │ │ new state S' is different from the current state S, where "different" means │ │ │ Erlang's strict inequality: =/= also known as "does not match". gen_statem │ │ │ does more things during state changes than during other state transitions.

        As A and S' depend only on S and E, the kind of state machine described │ │ │ here is a Mealy machine (see, for example, the Wikipedia article │ │ │ @@ -405,20 +405,20 @@ │ │ │ │ │ │ State Enter Calls │ │ │ │ │ │

        The gen_statem behaviour can, if this is enabled, regardless of callback │ │ │ mode, automatically call the state callback │ │ │ with special arguments whenever the state changes, so you can write │ │ │ state enter actions near the rest of the state transition rules. │ │ │ -It typically looks like this:

        StateName(enter, OldState, Data) ->
        │ │ │ +It typically looks like this:

        StateName(enter, OldState, Data) ->
        │ │ │      ... code for state enter actions here ...
        │ │ │ -    {keep_state, NewData};
        │ │ │ -StateName(EventType, EventContent, Data) ->
        │ │ │ +    {keep_state, NewData};
        │ │ │ +StateName(EventType, EventContent, Data) ->
        │ │ │      ... code for actions here ...
        │ │ │ -    {next_state, NewStateName, NewData}.

        Since the state enter call is not an event there are restrictions on the │ │ │ + {next_state, NewStateName, NewData}.

        Since the state enter call is not an event there are restrictions on the │ │ │ allowed return value and state transition actions. │ │ │ You must not change the state, postpone this non-event, │ │ │ insert any events, or change the │ │ │ callback module.

        The first state that is entered after gen_statem:init/1 will get │ │ │ a state enter call with OldState equal to the current state.

        You may repeat the state enter call using the {repeat_state,...} return │ │ │ value from the state callback. In this case │ │ │ OldState will also be equal to the current state.

        Depending on how your state machine is specified, this can be a very useful │ │ │ @@ -499,72 +499,72 @@ │ │ │ │ │ │ locked --> check_code : {button, Button}<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 store its length, and key buttons │ │ │ -that stores the collected buttons up to the same length.

        init(Code) ->
        │ │ │ -    do_lock(),
        │ │ │ -    Data = #{code => Code, length => length(Code), buttons => []},
        │ │ │ -    {ok, locked, Data}.

        Function gen_statem:start_link/3,4 │ │ │ +that stores the collected buttons up to the same length.

        init(Code) ->
        │ │ │ +    do_lock(),
        │ │ │ +    Data = #{code => Code, length => length(Code), buttons => []},
        │ │ │ +    {ok, locked, Data}.

        Function gen_statem:start_link/3,4 │ │ │ is synchronous. It does not return until the gen_statem is initialized │ │ │ and is ready to receive events.

        Function gen_statem:start_link/3,4 │ │ │ must be used if the gen_statem is part of a supervision tree, that is, │ │ │ started by a supervisor. Function, │ │ │ gen_statem:start/3,4 can be used to start │ │ │ a standalone gen_statem, meaning it is not part of a supervision tree.

        Function Module:callback_mode/0 selects │ │ │ the CallbackMode for the callback module, │ │ │ in this case state_functions. │ │ │ -That is, each state has its own handler function:

        callback_mode() ->
        │ │ │ +That is, each state has its own handler function:

        callback_mode() ->
        │ │ │      state_functions.

        │ │ │ │ │ │ │ │ │ │ │ │ Handling Events │ │ │

        │ │ │

        The function notifying the code lock about a button event is implemented using │ │ │ -gen_statem:cast/2:

        button(Button) ->
        │ │ │ -    gen_statem:cast(?NAME, {button,Button}).

        The first argument is the name of the gen_statem and must agree with │ │ │ +gen_statem:cast/2:

        button(Button) ->
        │ │ │ +    gen_statem:cast(?NAME, {button,Button}).

        The first argument is the name of the gen_statem and must agree with │ │ │ the name used to start it. So, we use the same macro ?NAME as when starting. │ │ │ {button, Button} is the event content.

        The event is sent to the gen_statem. When the event is received, the │ │ │ gen_statem calls StateName(cast, Event, Data), which is expected │ │ │ to return a tuple {next_state, NewStateName, NewData}, or │ │ │ {next_state, NewStateName, NewData, Actions}. StateName is the name │ │ │ of the current state and NewStateName is the name of the next state. │ │ │ NewData is a new value for the server data of the gen_statem, │ │ │ -and Actions is a list of actions to be performed by the gen_statem engine.

        locked(
        │ │ │ -  cast, {button,Button},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +and Actions is a list of actions to be performed by the gen_statem engine.

        locked(
        │ │ │ +  cast, {button,Button},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │      NewButtons =
        │ │ │          if
        │ │ │ -            length(Buttons) < Length ->
        │ │ │ +            length(Buttons) < Length ->
        │ │ │                  Buttons;
        │ │ │              true ->
        │ │ │ -                tl(Buttons)
        │ │ │ -        end ++ [Button],
        │ │ │ +                tl(Buttons)
        │ │ │ +        end ++ [Button],
        │ │ │      if
        │ │ │          NewButtons =:= Code -> % Correct
        │ │ │ -	    do_unlock(),
        │ │ │ -            {next_state, open, Data#{buttons := []},
        │ │ │ -             [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ +	    do_unlock(),
        │ │ │ +            {next_state, open, Data#{buttons := []},
        │ │ │ +             [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │  	true -> % Incomplete | Incorrect
        │ │ │ -            {next_state, locked, Data#{buttons := NewButtons}}
        │ │ │ +            {next_state, locked, Data#{buttons := NewButtons}}
        │ │ │      end.

        In state locked, when a button is pressed, it is collected with the │ │ │ previously pressed buttons up to the length of the correct code, then │ │ │ compared with the correct code. Depending on the result, the door is │ │ │ either unlocked and the gen_statem goes to state open, or the door │ │ │ remains in state locked.

        When changing to state open, the collected buttons are reset, the lock │ │ │ -unlocked, and a state time-out for 10 seconds is started.

        open(cast, {button,_}, Data) ->
        │ │ │ -    {next_state, open, Data}.

        In state open, a button event is ignored by staying in the same state. │ │ │ +unlocked, and a state time-out for 10 seconds is started.

        open(cast, {button,_}, Data) ->
        │ │ │ +    {next_state, open, Data}.

        In state open, a button event is ignored by staying in the same state. │ │ │ This can also be done by returning {keep_state, Data}, or in this case │ │ │ since Data is unchanged, by returning keep_state_and_data.

        │ │ │ │ │ │ │ │ │ │ │ │ State Time-Outs │ │ │

        │ │ │

        When a correct code has been given, the door is unlocked and the following │ │ │ -tuple is returned from locked/2:

        {next_state, open, Data#{buttons := []},
        │ │ │ - [{state_timeout,10_000,lock}]}; % Time in milliseconds

        10,000 is a time-out value in milliseconds. After this time (10 seconds), │ │ │ +tuple is returned from locked/2:

        {next_state, open, Data#{buttons := []},
        │ │ │ + [{state_timeout,10_000,lock}]}; % Time in milliseconds

        10,000 is a time-out value in milliseconds. After this time (10 seconds), │ │ │ a time-out occurs. Then, StateName(state_timeout, lock, Data) is called. │ │ │ The time-out occurs when the door has been in state open for 10 seconds. │ │ │ -After that the door is locked again:

        open(state_timeout, lock,  Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {next_state, locked, Data};

        The timer for a state time-out is automatically canceled when │ │ │ +After that the door is locked again:

        open(state_timeout, lock,  Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {next_state, locked, Data};

        The timer for a state time-out is automatically canceled when │ │ │ the state machine does a state change.

        You can restart, cancel, or update a state time-out. See section │ │ │ Time-Outs for details.

        │ │ │ │ │ │ │ │ │ │ │ │ All State Events │ │ │

        │ │ │

        Sometimes events can arrive in any state of the gen_statem. It is convenient │ │ │ to handle these in a common state handler function that all state functions │ │ │ call for events not specific to the state.

        Consider a code_length/0 function that returns the length │ │ │ of the correct code. We dispatch all events that are not state-specific │ │ │ to the common function handle_common/3:

        ...
        │ │ │ --export([button/1,code_length/0]).
        │ │ │ +-export([button/1,code_length/0]).
        │ │ │  ...
        │ │ │  
        │ │ │ -code_length() ->
        │ │ │ -    gen_statem:call(?NAME, code_length).
        │ │ │ +code_length() ->
        │ │ │ +    gen_statem:call(?NAME, code_length).
        │ │ │  
        │ │ │  ...
        │ │ │ -locked(...) -> ... ;
        │ │ │ -locked(EventType, EventContent, Data) ->
        │ │ │ -    handle_common(EventType, EventContent, Data).
        │ │ │ +locked(...) -> ... ;
        │ │ │ +locked(EventType, EventContent, Data) ->
        │ │ │ +    handle_common(EventType, EventContent, Data).
        │ │ │  
        │ │ │  ...
        │ │ │ -open(...) -> ... ;
        │ │ │ -open(EventType, EventContent, Data) ->
        │ │ │ -    handle_common(EventType, EventContent, Data).
        │ │ │ -
        │ │ │ -handle_common({call,From}, code_length, #{code := Code} = Data) ->
        │ │ │ -    {keep_state, Data,
        │ │ │ -     [{reply,From,length(Code)}]}.

        Another way to do it is through a convenience macro ?HANDLE_COMMON/0:

        ...
        │ │ │ --export([button/1,code_length/0]).
        │ │ │ +open(...) -> ... ;
        │ │ │ +open(EventType, EventContent, Data) ->
        │ │ │ +    handle_common(EventType, EventContent, Data).
        │ │ │ +
        │ │ │ +handle_common({call,From}, code_length, #{code := Code} = Data) ->
        │ │ │ +    {keep_state, Data,
        │ │ │ +     [{reply,From,length(Code)}]}.

        Another way to do it is through a convenience macro ?HANDLE_COMMON/0:

        ...
        │ │ │ +-export([button/1,code_length/0]).
        │ │ │  ...
        │ │ │  
        │ │ │ -code_length() ->
        │ │ │ -    gen_statem:call(?NAME, code_length).
        │ │ │ +code_length() ->
        │ │ │ +    gen_statem:call(?NAME, code_length).
        │ │ │  
        │ │ │ --define(HANDLE_COMMON,
        │ │ │ -    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, D)).
        │ │ │ +-define(HANDLE_COMMON,
        │ │ │ +    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, D)).
        │ │ │  %%
        │ │ │ -handle_common({call,From}, code_length, #{code := Code} = Data) ->
        │ │ │ -    {keep_state, Data,
        │ │ │ -     [{reply,From,length(Code)}]}.
        │ │ │ +handle_common({call,From}, code_length, #{code := Code} = Data) ->
        │ │ │ +    {keep_state, Data,
        │ │ │ +     [{reply,From,length(Code)}]}.
        │ │ │  
        │ │ │  ...
        │ │ │ -locked(...) -> ... ;
        │ │ │ +locked(...) -> ... ;
        │ │ │  ?HANDLE_COMMON.
        │ │ │  
        │ │ │  ...
        │ │ │ -open(...) -> ... ;
        │ │ │ +open(...) -> ... ;
        │ │ │  ?HANDLE_COMMON.

        This example uses gen_statem:call/2, which waits for a reply from the server. │ │ │ The reply is sent with a {reply, From, Reply} tuple in an action list in the │ │ │ {keep_state, ...} tuple that retains the current state. This return form is │ │ │ convenient when you want to stay in the current state but do not know or care │ │ │ about what it is.

        If the common state callback needs to know the current state a function │ │ │ -handle_common/4 can be used instead:

        -define(HANDLE_COMMON,
        │ │ │ -    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, ?FUNCTION_NAME, D)).

        │ │ │ +handle_common/4 can be used instead:

        -define(HANDLE_COMMON,
        │ │ │ +    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, ?FUNCTION_NAME, D)).

        │ │ │ │ │ │ │ │ │ │ │ │ One State Callback │ │ │

        │ │ │

        If callback mode handle_event_function is used, │ │ │ all events are handled in │ │ │ Module:handle_event/4 and we can │ │ │ (but do not have to) use an event-centered approach where we first branch │ │ │ depending on event and then depending on state:

        ...
        │ │ │ --export([handle_event/4]).
        │ │ │ +-export([handle_event/4]).
        │ │ │  
        │ │ │  ...
        │ │ │ -callback_mode() ->
        │ │ │ +callback_mode() ->
        │ │ │      handle_event_function.
        │ │ │  
        │ │ │ -handle_event(cast, {button,Button}, State, #{code := Code} = Data) ->
        │ │ │ +handle_event(cast, {button,Button}, State, #{code := Code} = Data) ->
        │ │ │      case State of
        │ │ │  	locked ->
        │ │ │ -            #{length := Length, buttons := Buttons} = Data,
        │ │ │ +            #{length := Length, buttons := Buttons} = Data,
        │ │ │              NewButtons =
        │ │ │                  if
        │ │ │ -                    length(Buttons) < Length ->
        │ │ │ +                    length(Buttons) < Length ->
        │ │ │                          Buttons;
        │ │ │                      true ->
        │ │ │ -                        tl(Buttons)
        │ │ │ -                end ++ [Button],
        │ │ │ +                        tl(Buttons)
        │ │ │ +                end ++ [Button],
        │ │ │              if
        │ │ │                  NewButtons =:= Code -> % Correct
        │ │ │ -                    do_unlock(),
        │ │ │ -                    {next_state, open, Data#{buttons := []},
        │ │ │ -                     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ +                    do_unlock(),
        │ │ │ +                    {next_state, open, Data#{buttons := []},
        │ │ │ +                     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │                  true -> % Incomplete | Incorrect
        │ │ │ -                    {keep_state, Data#{buttons := NewButtons}}
        │ │ │ +                    {keep_state, Data#{buttons := NewButtons}}
        │ │ │              end;
        │ │ │  	open ->
        │ │ │              keep_state_and_data
        │ │ │      end;
        │ │ │ -handle_event(state_timeout, lock, open, Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {next_state, locked, Data};
        │ │ │ -handle_event(
        │ │ │ -  {call,From}, code_length, _State, #{code := Code} = Data) ->
        │ │ │ -    {keep_state, Data,
        │ │ │ -     [{reply,From,length(Code)}]}.
        │ │ │ +handle_event(state_timeout, lock, open, Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {next_state, locked, Data};
        │ │ │ +handle_event(
        │ │ │ +  {call,From}, code_length, _State, #{code := Code} = Data) ->
        │ │ │ +    {keep_state, Data,
        │ │ │ +     [{reply,From,length(Code)}]}.
        │ │ │  
        │ │ │  ...

        │ │ │ │ │ │ │ │ │ │ │ │ Stopping │ │ │

        │ │ │ @@ -763,59 +763,59 @@ │ │ │ │ │ │

        If the gen_statem is part of a supervision tree, no stop function is needed. │ │ │ The gen_statem is automatically terminated by its supervisor. Exactly how │ │ │ this is done is defined by a shutdown strategy │ │ │ set in the supervisor.

        If it is necessary to clean up before termination, the shutdown strategy │ │ │ must be a time-out value and the gen_statem must in function init/1 │ │ │ set itself to trap exit signals by calling │ │ │ -process_flag(trap_exit, true):

        init(Args) ->
        │ │ │ -    process_flag(trap_exit, true),
        │ │ │ -    do_lock(),
        │ │ │ +process_flag(trap_exit, true):

        init(Args) ->
        │ │ │ +    process_flag(trap_exit, true),
        │ │ │ +    do_lock(),
        │ │ │      ...

        When ordered to shut down, the gen_statem then calls callback function │ │ │ terminate(shutdown, State, Data).

        In this example, function terminate/3 locks the door if it is open, │ │ │ so we do not accidentally leave the door open │ │ │ -when the supervision tree terminates:

        terminate(_Reason, State, _Data) ->
        │ │ │ -    State =/= locked andalso do_lock(),
        │ │ │ +when the supervision tree terminates:

        terminate(_Reason, State, _Data) ->
        │ │ │ +    State =/= locked andalso do_lock(),
        │ │ │      ok.

        │ │ │ │ │ │ │ │ │ │ │ │ Standalone gen_statem │ │ │

        │ │ │

        If the gen_statem is not part of a supervision tree, it can be stopped │ │ │ using gen_statem:stop/1, preferably through │ │ │ an API function:

        ...
        │ │ │ --export([start_link/1,stop/0]).
        │ │ │ +-export([start_link/1,stop/0]).
        │ │ │  
        │ │ │  ...
        │ │ │ -stop() ->
        │ │ │ -    gen_statem:stop(?NAME).

        This makes the gen_statem call callback function terminate/3 just like │ │ │ +stop() -> │ │ │ + gen_statem:stop(?NAME).

        This makes the gen_statem call callback function terminate/3 just like │ │ │ for a supervised server and waits for the process to terminate.

        │ │ │ │ │ │ │ │ │ │ │ │ Event Time-Outs │ │ │

        │ │ │

        A time-out feature inherited from gen_statem's predecessor gen_fsm, │ │ │ is an event time-out, that is, if an event arrives the timer is canceled. │ │ │ You get either an event or a time-out, but not both.

        It is ordered by the │ │ │ transition action {timeout, Time, EventContent}, │ │ │ or just an integer Time, even without the enclosing actions list (the latter │ │ │ is a form inherited from gen_fsm).

        This type of time-out is useful, for example, to act on inactivity. │ │ │ Let's restart the code sequence if no button is pressed for say 30 seconds:

        ...
        │ │ │  
        │ │ │ -locked(timeout, _, Data) ->
        │ │ │ -    {next_state, locked, Data#{buttons := []}};
        │ │ │ -locked(
        │ │ │ -  cast, {button,Button},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +locked(timeout, _, Data) ->
        │ │ │ +    {next_state, locked, Data#{buttons := []}};
        │ │ │ +locked(
        │ │ │ +  cast, {button,Button},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │  ...
        │ │ │  	true -> % Incomplete | Incorrect
        │ │ │ -            {next_state, locked, Data#{buttons := NewButtons},
        │ │ │ -             30_000} % Time in milliseconds
        │ │ │ +            {next_state, locked, Data#{buttons := NewButtons},
        │ │ │ +             30_000} % Time in milliseconds
        │ │ │  ...

        Whenever we receive a button event we start an event time-out of 30 seconds, │ │ │ and if we get an event type of timeout we reset the remaining │ │ │ code sequence.

        An event time-out is canceled by any other event so you either get │ │ │ some other event or the time-out event. Therefore, canceling, │ │ │ restarting, or updating an event time-out is neither possible nor │ │ │ necessary. Whatever event you act on has already canceled │ │ │ the event time-out, so there is never a running event time-out │ │ │ @@ -834,30 +834,30 @@ │ │ │ another, maybe cancel the time-out without changing states, or perhaps run │ │ │ multiple time-outs in parallel. All this can be accomplished with │ │ │ generic time-outs. They may look a little │ │ │ bit like event time-outs but contain │ │ │ a name to allow for any number of them simultaneously and they are │ │ │ not automatically canceled.

        Here is how to accomplish the state time-out in the previous example │ │ │ by instead using a generic time-out named for example open:

        ...
        │ │ │ -locked(
        │ │ │ -  cast, {button,Button},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +locked(
        │ │ │ +  cast, {button,Button},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │  ...
        │ │ │      if
        │ │ │          NewButtons =:= Code -> % Correct
        │ │ │ -	    do_unlock(),
        │ │ │ -            {next_state, open, Data#{buttons := []},
        │ │ │ -             [{{timeout,open},10_000,lock}]}; % Time in milliseconds
        │ │ │ +	    do_unlock(),
        │ │ │ +            {next_state, open, Data#{buttons := []},
        │ │ │ +             [{{timeout,open},10_000,lock}]}; % Time in milliseconds
        │ │ │  ...
        │ │ │  
        │ │ │ -open({timeout,open}, lock, Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {next_state,locked,Data};
        │ │ │ -open(cast, {button,_}, Data) ->
        │ │ │ -    {keep_state,Data};
        │ │ │ +open({timeout,open}, lock, Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {next_state,locked,Data};
        │ │ │ +open(cast, {button,_}, Data) ->
        │ │ │ +    {keep_state,Data};
        │ │ │  ...

        Specific generic time-outs can just as state time-outs │ │ │ be restarted or canceled by setting it to a new time or infinity.

        In this particular case we do not need to cancel the time-out since │ │ │ the time-out event is the only possible reason to do a state change │ │ │ from open to locked.

        Instead of bothering with when to cancel a time-out, a late time-out event │ │ │ can be handled by ignoring it if it arrives in a state │ │ │ where it is known to be late.

        You can restart, cancel, or update a generic time-out. │ │ │ See section Time-Outs for details.

        │ │ │ @@ -869,32 +869,32 @@ │ │ │

        The most versatile way to handle time-outs is to use Erlang Timers; see │ │ │ erlang:start_timer/3,4. Most time-out tasks │ │ │ can be performed with the time-out features in gen_statem, │ │ │ but an example of one that cannot is if you should need the return value │ │ │ from erlang:cancel_timer(Tref), that is, │ │ │ the remaining time of the timer.

        Here is how to accomplish the state time-out in the previous example │ │ │ by instead using an Erlang Timer:

        ...
        │ │ │ -locked(
        │ │ │ -  cast, {button,Button},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +locked(
        │ │ │ +  cast, {button,Button},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │  ...
        │ │ │      if
        │ │ │          NewButtons =:= Code -> % Correct
        │ │ │ -	    do_unlock(),
        │ │ │ +	    do_unlock(),
        │ │ │  	    Tref =
        │ │ │ -                 erlang:start_timer(
        │ │ │ -                     10_000, self(), lock), % Time in milliseconds
        │ │ │ -            {next_state, open, Data#{buttons := [], timer => Tref}};
        │ │ │ +                 erlang:start_timer(
        │ │ │ +                     10_000, self(), lock), % Time in milliseconds
        │ │ │ +            {next_state, open, Data#{buttons := [], timer => Tref}};
        │ │ │  ...
        │ │ │  
        │ │ │ -open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {next_state,locked,maps:remove(timer, Data)};
        │ │ │ -open(cast, {button,_}, Data) ->
        │ │ │ -    {keep_state,Data};
        │ │ │ +open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {next_state,locked,maps:remove(timer, Data)};
        │ │ │ +open(cast, {button,_}, Data) ->
        │ │ │ +    {keep_state,Data};
        │ │ │  ...

        Removing the timer key from the map when we do a state change to locked │ │ │ is not strictly necessary since we can only get into state open │ │ │ with an updated timer map value. But it can be nice to not have │ │ │ outdated values in the state Data.

        If you need to cancel a timer because of some other event, you can use │ │ │ erlang:cancel_timer(Tref). Note that no time-out │ │ │ message will arrive after this (because the timer has been │ │ │ explicitly canceled), unless you have already postponed one earlier │ │ │ @@ -910,16 +910,16 @@ │ │ │ Postponing Events │ │ │

        │ │ │

        If you want to ignore a particular event in the current state and handle it │ │ │ in a future state, you can postpone the event. A postponed event │ │ │ is retried after a state change, that is, OldState =/= NewState.

        Postponing is ordered by the │ │ │ transition action postpone.

        In this example, instead of ignoring button events while in the open state, │ │ │ we can postpone them handle them later in the locked state:

        ...
        │ │ │ -open(cast, {button,_}, Data) ->
        │ │ │ -    {keep_state,Data,[postpone]};
        │ │ │ +open(cast, {button,_}, Data) ->
        │ │ │ +    {keep_state,Data,[postpone]};
        │ │ │  ...

        Since a postponed event is only retried after a state change, you have to │ │ │ think about where to keep a state data item. You can keep it in the server │ │ │ Data or in the State itself, for example by having two more or less │ │ │ identical states to keep a boolean value, or by using a complex state (see │ │ │ section Complex State) with │ │ │ callback mode │ │ │ handle_event_function. If a change │ │ │ @@ -940,55 +940,55 @@ │ │ │ │ │ │ │ │ │ │ │ │ Selective Receive │ │ │ │ │ │

        Erlang's selective receive statement is often used to describe simple state │ │ │ machine examples in straightforward Erlang code. The following is a possible │ │ │ -implementation of the first example:

        -module(code_lock).
        │ │ │ --define(NAME, code_lock_1).
        │ │ │ --export([start_link/1,button/1]).
        │ │ │ -
        │ │ │ -start_link(Code) ->
        │ │ │ -    spawn(
        │ │ │ -      fun () ->
        │ │ │ -	      true = register(?NAME, self()),
        │ │ │ -	      do_lock(),
        │ │ │ -	      locked(Code, length(Code), [])
        │ │ │ -      end).
        │ │ │ +implementation of the first example:

        -module(code_lock).
        │ │ │ +-define(NAME, code_lock_1).
        │ │ │ +-export([start_link/1,button/1]).
        │ │ │ +
        │ │ │ +start_link(Code) ->
        │ │ │ +    spawn(
        │ │ │ +      fun () ->
        │ │ │ +	      true = register(?NAME, self()),
        │ │ │ +	      do_lock(),
        │ │ │ +	      locked(Code, length(Code), [])
        │ │ │ +      end).
        │ │ │  
        │ │ │ -button(Button) ->
        │ │ │ -    ?NAME ! {button,Button}.
        locked(Code, Length, Buttons) ->
        │ │ │ +button(Button) ->
        │ │ │ +    ?NAME ! {button,Button}.
        locked(Code, Length, Buttons) ->
        │ │ │      receive
        │ │ │ -        {button,Button} ->
        │ │ │ +        {button,Button} ->
        │ │ │              NewButtons =
        │ │ │                  if
        │ │ │ -                    length(Buttons) < Length ->
        │ │ │ +                    length(Buttons) < Length ->
        │ │ │                          Buttons;
        │ │ │                      true ->
        │ │ │ -                        tl(Buttons)
        │ │ │ -                end ++ [Button],
        │ │ │ +                        tl(Buttons)
        │ │ │ +                end ++ [Button],
        │ │ │              if
        │ │ │                  NewButtons =:= Code -> % Correct
        │ │ │ -                    do_unlock(),
        │ │ │ -		    open(Code, Length);
        │ │ │ +                    do_unlock(),
        │ │ │ +		    open(Code, Length);
        │ │ │                  true -> % Incomplete | Incorrect
        │ │ │ -                    locked(Code, Length, NewButtons)
        │ │ │ +                    locked(Code, Length, NewButtons)
        │ │ │              end
        │ │ │ -    end.
        open(Code, Length) ->
        │ │ │ +    end.
        open(Code, Length) ->
        │ │ │      receive
        │ │ │      after 10_000 -> % Time in milliseconds
        │ │ │ -	    do_lock(),
        │ │ │ -	    locked(Code, Length, [])
        │ │ │ +	    do_lock(),
        │ │ │ +	    locked(Code, Length, [])
        │ │ │      end.
        │ │ │  
        │ │ │ -do_lock() ->
        │ │ │ -    io:format("Locked~n", []).
        │ │ │ -do_unlock() ->
        │ │ │ -    io:format("Open~n", []).

        The selective receive in this case causes open to implicitly postpone any │ │ │ +do_lock() -> │ │ │ + io:format("Locked~n", []). │ │ │ +do_unlock() -> │ │ │ + io:format("Open~n", []).

        The selective receive in this case causes open to implicitly postpone any │ │ │ events to the locked state.

        A catch-all receive should never be used from a gen_statem behaviour │ │ │ (or from any gen_* behaviour), as the receive statement is within │ │ │ the gen_* engine itself. sys-compatible behaviours must respond to │ │ │ system messages and therefore do that in their engine receive loop, │ │ │ passing non-system messages to the callback module. Using a catch-all │ │ │ receive can result in system messages being discarded, which in turn │ │ │ can lead to unexpected behaviour. If a selective receive must be used, │ │ │ @@ -1011,40 +1011,40 @@ │ │ │ section), especially if only one or a few states have state enter actions, │ │ │ this is a perfect use case for the built in │ │ │ state enter calls.

        You return a list containing state_enter from your │ │ │ callback_mode/0 function and the │ │ │ gen_statem engine will call your state callback once with an event │ │ │ (enter, OldState, ...) whenever it does a state change. Then you │ │ │ just need to handle these event-like calls in all states.

        ...
        │ │ │ -init(Code) ->
        │ │ │ -    process_flag(trap_exit, true),
        │ │ │ -    Data = #{code => Code, length = length(Code)},
        │ │ │ -    {ok, locked, Data}.
        │ │ │ -
        │ │ │ -callback_mode() ->
        │ │ │ -    [state_functions,state_enter].
        │ │ │ -
        │ │ │ -locked(enter, _OldState, Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {keep_state,Data#{buttons => []}};
        │ │ │ -locked(
        │ │ │ -  cast, {button,Button},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +init(Code) ->
        │ │ │ +    process_flag(trap_exit, true),
        │ │ │ +    Data = #{code => Code, length = length(Code)},
        │ │ │ +    {ok, locked, Data}.
        │ │ │ +
        │ │ │ +callback_mode() ->
        │ │ │ +    [state_functions,state_enter].
        │ │ │ +
        │ │ │ +locked(enter, _OldState, Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {keep_state,Data#{buttons => []}};
        │ │ │ +locked(
        │ │ │ +  cast, {button,Button},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │  ...
        │ │ │      if
        │ │ │          NewButtons =:= Code -> % Correct
        │ │ │ -            {next_state, open, Data};
        │ │ │ +            {next_state, open, Data};
        │ │ │  ...
        │ │ │  
        │ │ │ -open(enter, _OldState, _Data) ->
        │ │ │ -    do_unlock(),
        │ │ │ -    {keep_state_and_data,
        │ │ │ -     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ -open(state_timeout, lock, Data) ->
        │ │ │ -    {next_state, locked, Data};
        │ │ │ +open(enter, _OldState, _Data) ->
        │ │ │ +    do_unlock(),
        │ │ │ +    {keep_state_and_data,
        │ │ │ +     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ +open(state_timeout, lock, Data) ->
        │ │ │ +    {next_state, locked, Data};
        │ │ │  ...

        You can repeat the state enter code by returning one of │ │ │ {repeat_state, ...},{repeat_state_and_data, _}, │ │ │ or repeat_state_and_data that otherwise behaves exactly like their │ │ │ keep_state siblings. See the type │ │ │ state_callback_result() │ │ │ in the Reference Manual.

        │ │ │ │ │ │ @@ -1066,44 +1066,44 @@ │ │ │ to dispatch pre-processed events as internal events to the main state │ │ │ machine.

        Using internal events also can make it easier to synchronize the state │ │ │ machines.

        A variant of this is to use a complex state with │ │ │ one state callback, modeling the state │ │ │ with, for example, a tuple {MainFSMState, SubFSMState}.

        To illustrate this we make up an example where the buttons instead generate │ │ │ down and up (press and release) events, and the lock responds │ │ │ to an up event only after the corresponding down event.

        ...
        │ │ │ --export([down/1, up/1]).
        │ │ │ +-export([down/1, up/1]).
        │ │ │  ...
        │ │ │ -down(Button) ->
        │ │ │ -    gen_statem:cast(?NAME, {down,Button}).
        │ │ │ +down(Button) ->
        │ │ │ +    gen_statem:cast(?NAME, {down,Button}).
        │ │ │  
        │ │ │ -up(Button) ->
        │ │ │ -    gen_statem:cast(?NAME, {up,Button}).
        │ │ │ +up(Button) ->
        │ │ │ +    gen_statem:cast(?NAME, {up,Button}).
        │ │ │  
        │ │ │  ...
        │ │ │  
        │ │ │ -locked(enter, _OldState, Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {keep_state,Data#{buttons => []}};
        │ │ │ -locked(
        │ │ │ -  internal, {button,Button},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ -...
        handle_common(cast, {down,Button}, Data) ->
        │ │ │ -    {keep_state, Data#{button => Button}};
        │ │ │ -handle_common(cast, {up,Button}, Data) ->
        │ │ │ +locked(enter, _OldState, Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {keep_state,Data#{buttons => []}};
        │ │ │ +locked(
        │ │ │ +  internal, {button,Button},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +...
        handle_common(cast, {down,Button}, Data) ->
        │ │ │ +    {keep_state, Data#{button => Button}};
        │ │ │ +handle_common(cast, {up,Button}, Data) ->
        │ │ │      case Data of
        │ │ │ -        #{button := Button} ->
        │ │ │ -            {keep_state,maps:remove(button, Data),
        │ │ │ -             [{next_event,internal,{button,Button}}]};
        │ │ │ -        #{} ->
        │ │ │ +        #{button := Button} ->
        │ │ │ +            {keep_state,maps:remove(button, Data),
        │ │ │ +             [{next_event,internal,{button,Button}}]};
        │ │ │ +        #{} ->
        │ │ │              keep_state_and_data
        │ │ │      end;
        │ │ │  ...
        │ │ │  
        │ │ │ -open(internal, {button,_}, Data) ->
        │ │ │ -    {keep_state,Data,[postpone]};
        │ │ │ +open(internal, {button,_}, Data) ->
        │ │ │ +    {keep_state,Data,[postpone]};
        │ │ │  ...

        If you start this program with code_lock:start([17]) you can unlock with │ │ │ code_lock:down(17), code_lock:up(17).

        │ │ │ │ │ │ │ │ │ │ │ │ Example Revisited │ │ │

        │ │ │ @@ -1131,152 +1131,152 @@ │ │ │ Also, the state diagram does not show that the code_length/0 call │ │ │ must be handled in every state.

        │ │ │ │ │ │ │ │ │ │ │ │ Callback Mode: state_functions │ │ │

        │ │ │ -

        Using state functions:

        -module(code_lock).
        │ │ │ --behaviour(gen_statem).
        │ │ │ --define(NAME, code_lock_2).
        │ │ │ +

        Using state functions:

        -module(code_lock).
        │ │ │ +-behaviour(gen_statem).
        │ │ │ +-define(NAME, code_lock_2).
        │ │ │  
        │ │ │ --export([start_link/1,stop/0]).
        │ │ │ --export([down/1,up/1,code_length/0]).
        │ │ │ --export([init/1,callback_mode/0,terminate/3]).
        │ │ │ --export([locked/3,open/3]).
        │ │ │ -
        │ │ │ -start_link(Code) ->
        │ │ │ -    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
        │ │ │ -stop() ->
        │ │ │ -    gen_statem:stop(?NAME).
        │ │ │ -
        │ │ │ -down(Button) ->
        │ │ │ -    gen_statem:cast(?NAME, {down,Button}).
        │ │ │ -up(Button) ->
        │ │ │ -    gen_statem:cast(?NAME, {up,Button}).
        │ │ │ -code_length() ->
        │ │ │ -    gen_statem:call(?NAME, code_length).
        init(Code) ->
        │ │ │ -    process_flag(trap_exit, true),
        │ │ │ -    Data = #{code => Code, length => length(Code), buttons => []},
        │ │ │ -    {ok, locked, Data}.
        │ │ │ +-export([start_link/1,stop/0]).
        │ │ │ +-export([down/1,up/1,code_length/0]).
        │ │ │ +-export([init/1,callback_mode/0,terminate/3]).
        │ │ │ +-export([locked/3,open/3]).
        │ │ │ +
        │ │ │ +start_link(Code) ->
        │ │ │ +    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
        │ │ │ +stop() ->
        │ │ │ +    gen_statem:stop(?NAME).
        │ │ │ +
        │ │ │ +down(Button) ->
        │ │ │ +    gen_statem:cast(?NAME, {down,Button}).
        │ │ │ +up(Button) ->
        │ │ │ +    gen_statem:cast(?NAME, {up,Button}).
        │ │ │ +code_length() ->
        │ │ │ +    gen_statem:call(?NAME, code_length).
        init(Code) ->
        │ │ │ +    process_flag(trap_exit, true),
        │ │ │ +    Data = #{code => Code, length => length(Code), buttons => []},
        │ │ │ +    {ok, locked, Data}.
        │ │ │  
        │ │ │ -callback_mode() ->
        │ │ │ -    [state_functions,state_enter].
        │ │ │ +callback_mode() ->
        │ │ │ +    [state_functions,state_enter].
        │ │ │  
        │ │ │ --define(HANDLE_COMMON,
        │ │ │ -    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, D)).
        │ │ │ +-define(HANDLE_COMMON,
        │ │ │ +    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, D)).
        │ │ │  %%
        │ │ │ -handle_common(cast, {down,Button}, Data) ->
        │ │ │ -    {keep_state, Data#{button => Button}};
        │ │ │ -handle_common(cast, {up,Button}, Data) ->
        │ │ │ +handle_common(cast, {down,Button}, Data) ->
        │ │ │ +    {keep_state, Data#{button => Button}};
        │ │ │ +handle_common(cast, {up,Button}, Data) ->
        │ │ │      case Data of
        │ │ │ -        #{button := Button} ->
        │ │ │ -            {keep_state, maps:remove(button, Data),
        │ │ │ -             [{next_event,internal,{button,Button}}]};
        │ │ │ -        #{} ->
        │ │ │ +        #{button := Button} ->
        │ │ │ +            {keep_state, maps:remove(button, Data),
        │ │ │ +             [{next_event,internal,{button,Button}}]};
        │ │ │ +        #{} ->
        │ │ │              keep_state_and_data
        │ │ │      end;
        │ │ │ -handle_common({call,From}, code_length, #{code := Code}) ->
        │ │ │ -    {keep_state_and_data,
        │ │ │ -     [{reply,From,length(Code)}]}.
        locked(enter, _OldState, Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {keep_state, Data#{buttons := []}};
        │ │ │ -locked(state_timeout, button, Data) ->
        │ │ │ -    {keep_state, Data#{buttons := []}};
        │ │ │ -locked(
        │ │ │ -  internal, {button,Button},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +handle_common({call,From}, code_length, #{code := Code}) ->
        │ │ │ +    {keep_state_and_data,
        │ │ │ +     [{reply,From,length(Code)}]}.
        locked(enter, _OldState, Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {keep_state, Data#{buttons := []}};
        │ │ │ +locked(state_timeout, button, Data) ->
        │ │ │ +    {keep_state, Data#{buttons := []}};
        │ │ │ +locked(
        │ │ │ +  internal, {button,Button},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │      NewButtons =
        │ │ │          if
        │ │ │ -            length(Buttons) < Length ->
        │ │ │ +            length(Buttons) < Length ->
        │ │ │                  Buttons;
        │ │ │              true ->
        │ │ │ -                tl(Buttons)
        │ │ │ -        end ++ [Button],
        │ │ │ +                tl(Buttons)
        │ │ │ +        end ++ [Button],
        │ │ │      if
        │ │ │          NewButtons =:= Code -> % Correct
        │ │ │ -            {next_state, open, Data};
        │ │ │ +            {next_state, open, Data};
        │ │ │  	true -> % Incomplete | Incorrect
        │ │ │ -            {keep_state, Data#{buttons := NewButtons},
        │ │ │ -             [{state_timeout,30_000,button}]} % Time in milliseconds
        │ │ │ +            {keep_state, Data#{buttons := NewButtons},
        │ │ │ +             [{state_timeout,30_000,button}]} % Time in milliseconds
        │ │ │      end;
        │ │ │ -?HANDLE_COMMON.
        open(enter, _OldState, _Data) ->
        │ │ │ -    do_unlock(),
        │ │ │ -    {keep_state_and_data,
        │ │ │ -     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ -open(state_timeout, lock, Data) ->
        │ │ │ -    {next_state, locked, Data};
        │ │ │ -open(internal, {button,_}, _) ->
        │ │ │ -    {keep_state_and_data, [postpone]};
        │ │ │ +?HANDLE_COMMON.
        open(enter, _OldState, _Data) ->
        │ │ │ +    do_unlock(),
        │ │ │ +    {keep_state_and_data,
        │ │ │ +     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ +open(state_timeout, lock, Data) ->
        │ │ │ +    {next_state, locked, Data};
        │ │ │ +open(internal, {button,_}, _) ->
        │ │ │ +    {keep_state_and_data, [postpone]};
        │ │ │  ?HANDLE_COMMON.
        │ │ │  
        │ │ │ -do_lock() ->
        │ │ │ -    io:format("Locked~n", []).
        │ │ │ -do_unlock() ->
        │ │ │ -    io:format("Open~n", []).
        │ │ │ +do_lock() ->
        │ │ │ +    io:format("Locked~n", []).
        │ │ │ +do_unlock() ->
        │ │ │ +    io:format("Open~n", []).
        │ │ │  
        │ │ │ -terminate(_Reason, State, _Data) ->
        │ │ │ -    State =/= locked andalso do_lock(),
        │ │ │ +terminate(_Reason, State, _Data) ->
        │ │ │ +    State =/= locked andalso do_lock(),
        │ │ │      ok.

        │ │ │ │ │ │ │ │ │ │ │ │ Callback Mode: handle_event_function │ │ │

        │ │ │

        This section describes what to change in the example to use one │ │ │ handle_event/4 function. The previously used approach to first branch │ │ │ depending on event does not work that well here because of │ │ │ -the state enter calls, so this example first branches depending on state:

        -export([handle_event/4]).
        callback_mode() ->
        │ │ │ -    [handle_event_function,state_enter].
        %%
        │ │ │ +the state enter calls, so this example first branches depending on state:

        -export([handle_event/4]).
        callback_mode() ->
        │ │ │ +    [handle_event_function,state_enter].
        %%
        │ │ │  %% State: locked
        │ │ │ -handle_event(enter, _OldState, locked, Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {keep_state, Data#{buttons := []}};
        │ │ │ -handle_event(state_timeout, button, locked, Data) ->
        │ │ │ -    {keep_state, Data#{buttons := []}};
        │ │ │ -handle_event(
        │ │ │ -  internal, {button,Button}, locked,
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +handle_event(enter, _OldState, locked, Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {keep_state, Data#{buttons := []}};
        │ │ │ +handle_event(state_timeout, button, locked, Data) ->
        │ │ │ +    {keep_state, Data#{buttons := []}};
        │ │ │ +handle_event(
        │ │ │ +  internal, {button,Button}, locked,
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │      NewButtons =
        │ │ │          if
        │ │ │ -            length(Buttons) < Length ->
        │ │ │ +            length(Buttons) < Length ->
        │ │ │                  Buttons;
        │ │ │              true ->
        │ │ │ -                tl(Buttons)
        │ │ │ -        end ++ [Button],
        │ │ │ +                tl(Buttons)
        │ │ │ +        end ++ [Button],
        │ │ │      if
        │ │ │          NewButtons =:= Code -> % Correct
        │ │ │ -            {next_state, open, Data};
        │ │ │ +            {next_state, open, Data};
        │ │ │  	true -> % Incomplete | Incorrect
        │ │ │ -            {keep_state, Data#{buttons := NewButtons},
        │ │ │ -             [{state_timeout,30_000,button}]} % Time in milliseconds
        │ │ │ +            {keep_state, Data#{buttons := NewButtons},
        │ │ │ +             [{state_timeout,30_000,button}]} % Time in milliseconds
        │ │ │      end;
        %%
        │ │ │  %% State: open
        │ │ │ -handle_event(enter, _OldState, open, _Data) ->
        │ │ │ -    do_unlock(),
        │ │ │ -    {keep_state_and_data,
        │ │ │ -     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ -handle_event(state_timeout, lock, open, Data) ->
        │ │ │ -    {next_state, locked, Data};
        │ │ │ -handle_event(internal, {button,_}, open, _) ->
        │ │ │ -    {keep_state_and_data,[postpone]};
        %% Common events
        │ │ │ -handle_event(cast, {down,Button}, _State, Data) ->
        │ │ │ -    {keep_state, Data#{button => Button}};
        │ │ │ -handle_event(cast, {up,Button}, _State, Data) ->
        │ │ │ +handle_event(enter, _OldState, open, _Data) ->
        │ │ │ +    do_unlock(),
        │ │ │ +    {keep_state_and_data,
        │ │ │ +     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ +handle_event(state_timeout, lock, open, Data) ->
        │ │ │ +    {next_state, locked, Data};
        │ │ │ +handle_event(internal, {button,_}, open, _) ->
        │ │ │ +    {keep_state_and_data,[postpone]};
        %% Common events
        │ │ │ +handle_event(cast, {down,Button}, _State, Data) ->
        │ │ │ +    {keep_state, Data#{button => Button}};
        │ │ │ +handle_event(cast, {up,Button}, _State, Data) ->
        │ │ │      case Data of
        │ │ │ -        #{button := Button} ->
        │ │ │ -            {keep_state, maps:remove(button, Data),
        │ │ │ -             [{next_event,internal,{button,Button}},
        │ │ │ -              {state_timeout,30_000,button}]}; % Time in milliseconds
        │ │ │ -        #{} ->
        │ │ │ +        #{button := Button} ->
        │ │ │ +            {keep_state, maps:remove(button, Data),
        │ │ │ +             [{next_event,internal,{button,Button}},
        │ │ │ +              {state_timeout,30_000,button}]}; % Time in milliseconds
        │ │ │ +        #{} ->
        │ │ │              keep_state_and_data
        │ │ │      end;
        │ │ │ -handle_event({call,From}, code_length, _State, #{length := Length}) ->
        │ │ │ -    {keep_state_and_data,
        │ │ │ -     [{reply,From,Length}]}.

        Notice that postponing buttons from the open state to the locked state │ │ │ +handle_event({call,From}, code_length, _State, #{length := Length}) -> │ │ │ + {keep_state_and_data, │ │ │ + [{reply,From,Length}]}.

        Notice that postponing buttons from the open state to the locked state │ │ │ seems like a strange thing to do for a code lock, but it at least │ │ │ illustrates event postponing.

        │ │ │ │ │ │ │ │ │ │ │ │ Filter the State │ │ │

        │ │ │ @@ -1286,30 +1286,30 @@ │ │ │ and which digits that remain to unlock.

        This state data can be regarded as sensitive, and maybe not what you want │ │ │ in the error log because of some unpredictable event.

        Another reason to filter the state can be that the state is too large to print, │ │ │ as it fills the error log with uninteresting details.

        To avoid this, you can format the internal state that gets in the error log │ │ │ and gets returned from sys:get_status/1,2 │ │ │ by implementing function │ │ │ Module:format_status/2, │ │ │ for example like this:

        ...
        │ │ │ --export([init/1,terminate/3,format_status/2]).
        │ │ │ +-export([init/1,terminate/3,format_status/2]).
        │ │ │  ...
        │ │ │  
        │ │ │ -format_status(Opt, [_PDict,State,Data]) ->
        │ │ │ +format_status(Opt, [_PDict,State,Data]) ->
        │ │ │      StateData =
        │ │ │ -	{State,
        │ │ │ -	 maps:filter(
        │ │ │ -	   fun (code, _) -> false;
        │ │ │ -	       (_, _) -> true
        │ │ │ +	{State,
        │ │ │ +	 maps:filter(
        │ │ │ +	   fun (code, _) -> false;
        │ │ │ +	       (_, _) -> true
        │ │ │  	   end,
        │ │ │ -	   Data)},
        │ │ │ +	   Data)},
        │ │ │      case Opt of
        │ │ │  	terminate ->
        │ │ │  	    StateData;
        │ │ │  	normal ->
        │ │ │ -	    [{data,[{"State",StateData}]}]
        │ │ │ +	    [{data,[{"State",StateData}]}]
        │ │ │      end.

        It is not mandatory to implement a │ │ │ Module:format_status/2 function. │ │ │ If you do not, a default implementation is used that does the same │ │ │ as this example function without filtering the Data term, that is, │ │ │ StateData = {State, Data}, in this example containing sensitive information.

        │ │ │ │ │ │ │ │ │ @@ -1322,104 +1322,104 @@ │ │ │ like a tuple.

        One reason to use this is when you have a state item that when changed │ │ │ should cancel the state time-out, or one that affects │ │ │ the event handling in combination with postponing events. We will go for │ │ │ the latter and complicate the previous example by introducing │ │ │ a configurable lock button (this is the state item in question), │ │ │ which in the open state immediately locks the door, and an API function │ │ │ set_lock_button/1 to set the lock button.

        Suppose now that we call set_lock_button while the door is open, │ │ │ -and we have already postponed a button event that was the new lock button:

        1> code_lock:start_link([a,b,c], x).
        │ │ │ -{ok,<0.666.0>}
        │ │ │ -2> code_lock:button(a).
        │ │ │ +and we have already postponed a button event that was the new lock button:

        1> code_lock:start_link([a,b,c], x).
        │ │ │ +{ok,<0.666.0>}
        │ │ │ +2> code_lock:button(a).
        │ │ │  ok
        │ │ │ -3> code_lock:button(b).
        │ │ │ +3> code_lock:button(b).
        │ │ │  ok
        │ │ │ -4> code_lock:button(c).
        │ │ │ +4> code_lock:button(c).
        │ │ │  ok
        │ │ │  Open
        │ │ │ -5> code_lock:button(y).
        │ │ │ +5> code_lock:button(y).
        │ │ │  ok
        │ │ │ -6> code_lock:set_lock_button(y).
        │ │ │ +6> code_lock:set_lock_button(y).
        │ │ │  x
        │ │ │  % What should happen here?  Immediate lock or nothing?

        We could say that the button was pressed too early so it should not be │ │ │ recognized as the lock button. Or we can make the lock button part of │ │ │ the state so when we then change the lock button in the locked state, │ │ │ the change becomes a state change and all postponed events are retried, │ │ │ therefore the lock is immediately locked!

        We define the state as {StateName, LockButton}, where StateName │ │ │ -is as before and LockButton is the current lock button:

        -module(code_lock).
        │ │ │ --behaviour(gen_statem).
        │ │ │ --define(NAME, code_lock_3).
        │ │ │ +is as before and LockButton is the current lock button:

        -module(code_lock).
        │ │ │ +-behaviour(gen_statem).
        │ │ │ +-define(NAME, code_lock_3).
        │ │ │  
        │ │ │ --export([start_link/2,stop/0]).
        │ │ │ --export([button/1,set_lock_button/1]).
        │ │ │ --export([init/1,callback_mode/0,terminate/3]).
        │ │ │ --export([handle_event/4]).
        │ │ │ -
        │ │ │ -start_link(Code, LockButton) ->
        │ │ │ -    gen_statem:start_link(
        │ │ │ -        {local,?NAME}, ?MODULE, {Code,LockButton}, []).
        │ │ │ -stop() ->
        │ │ │ -    gen_statem:stop(?NAME).
        │ │ │ -
        │ │ │ -button(Button) ->
        │ │ │ -    gen_statem:cast(?NAME, {button,Button}).
        │ │ │ -set_lock_button(LockButton) ->
        │ │ │ -    gen_statem:call(?NAME, {set_lock_button,LockButton}).
        init({Code,LockButton}) ->
        │ │ │ -    process_flag(trap_exit, true),
        │ │ │ -    Data = #{code => Code, length => length(Code), buttons => []},
        │ │ │ -    {ok, {locked,LockButton}, Data}.
        │ │ │ +-export([start_link/2,stop/0]).
        │ │ │ +-export([button/1,set_lock_button/1]).
        │ │ │ +-export([init/1,callback_mode/0,terminate/3]).
        │ │ │ +-export([handle_event/4]).
        │ │ │ +
        │ │ │ +start_link(Code, LockButton) ->
        │ │ │ +    gen_statem:start_link(
        │ │ │ +        {local,?NAME}, ?MODULE, {Code,LockButton}, []).
        │ │ │ +stop() ->
        │ │ │ +    gen_statem:stop(?NAME).
        │ │ │ +
        │ │ │ +button(Button) ->
        │ │ │ +    gen_statem:cast(?NAME, {button,Button}).
        │ │ │ +set_lock_button(LockButton) ->
        │ │ │ +    gen_statem:call(?NAME, {set_lock_button,LockButton}).
        init({Code,LockButton}) ->
        │ │ │ +    process_flag(trap_exit, true),
        │ │ │ +    Data = #{code => Code, length => length(Code), buttons => []},
        │ │ │ +    {ok, {locked,LockButton}, Data}.
        │ │ │  
        │ │ │ -callback_mode() ->
        │ │ │ -    [handle_event_function,state_enter].
        │ │ │ +callback_mode() ->
        │ │ │ +    [handle_event_function,state_enter].
        │ │ │  
        │ │ │  %% State: locked
        │ │ │ -handle_event(enter, _OldState, {locked,_}, Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {keep_state, Data#{buttons := []}};
        │ │ │ -handle_event(state_timeout, button, {locked,_}, Data) ->
        │ │ │ -    {keep_state, Data#{buttons := []}};
        │ │ │ -handle_event(
        │ │ │ -  cast, {button,Button}, {locked,LockButton},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +handle_event(enter, _OldState, {locked,_}, Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {keep_state, Data#{buttons := []}};
        │ │ │ +handle_event(state_timeout, button, {locked,_}, Data) ->
        │ │ │ +    {keep_state, Data#{buttons := []}};
        │ │ │ +handle_event(
        │ │ │ +  cast, {button,Button}, {locked,LockButton},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │      NewButtons =
        │ │ │          if
        │ │ │ -            length(Buttons) < Length ->
        │ │ │ +            length(Buttons) < Length ->
        │ │ │                  Buttons;
        │ │ │              true ->
        │ │ │ -                tl(Buttons)
        │ │ │ -        end ++ [Button],
        │ │ │ +                tl(Buttons)
        │ │ │ +        end ++ [Button],
        │ │ │      if
        │ │ │          NewButtons =:= Code -> % Correct
        │ │ │ -            {next_state, {open,LockButton}, Data};
        │ │ │ +            {next_state, {open,LockButton}, Data};
        │ │ │  	true -> % Incomplete | Incorrect
        │ │ │ -            {keep_state, Data#{buttons := NewButtons},
        │ │ │ -             [{state_timeout,30_000,button}]} % Time in milliseconds
        │ │ │ +            {keep_state, Data#{buttons := NewButtons},
        │ │ │ +             [{state_timeout,30_000,button}]} % Time in milliseconds
        │ │ │      end;
        %%
        │ │ │  %% State: open
        │ │ │ -handle_event(enter, _OldState, {open,_}, _Data) ->
        │ │ │ -    do_unlock(),
        │ │ │ -    {keep_state_and_data,
        │ │ │ -     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ -handle_event(state_timeout, lock, {open,LockButton}, Data) ->
        │ │ │ -    {next_state, {locked,LockButton}, Data};
        │ │ │ -handle_event(cast, {button,LockButton}, {open,LockButton}, Data) ->
        │ │ │ -    {next_state, {locked,LockButton}, Data};
        │ │ │ -handle_event(cast, {button,_}, {open,_}, _Data) ->
        │ │ │ -    {keep_state_and_data,[postpone]};
        %%
        │ │ │ +handle_event(enter, _OldState, {open,_}, _Data) ->
        │ │ │ +    do_unlock(),
        │ │ │ +    {keep_state_and_data,
        │ │ │ +     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ +handle_event(state_timeout, lock, {open,LockButton}, Data) ->
        │ │ │ +    {next_state, {locked,LockButton}, Data};
        │ │ │ +handle_event(cast, {button,LockButton}, {open,LockButton}, Data) ->
        │ │ │ +    {next_state, {locked,LockButton}, Data};
        │ │ │ +handle_event(cast, {button,_}, {open,_}, _Data) ->
        │ │ │ +    {keep_state_and_data,[postpone]};
        %%
        │ │ │  %% Common events
        │ │ │ -handle_event(
        │ │ │ -  {call,From}, {set_lock_button,NewLockButton},
        │ │ │ -  {StateName,OldLockButton}, Data) ->
        │ │ │ -    {next_state, {StateName,NewLockButton}, Data,
        │ │ │ -     [{reply,From,OldLockButton}]}.
        do_lock() ->
        │ │ │ -    io:format("Locked~n", []).
        │ │ │ -do_unlock() ->
        │ │ │ -    io:format("Open~n", []).
        │ │ │ +handle_event(
        │ │ │ +  {call,From}, {set_lock_button,NewLockButton},
        │ │ │ +  {StateName,OldLockButton}, Data) ->
        │ │ │ +    {next_state, {StateName,NewLockButton}, Data,
        │ │ │ +     [{reply,From,OldLockButton}]}.
        do_lock() ->
        │ │ │ +    io:format("Locked~n", []).
        │ │ │ +do_unlock() ->
        │ │ │ +    io:format("Open~n", []).
        │ │ │  
        │ │ │ -terminate(_Reason, State, _Data) ->
        │ │ │ -    State =/= locked andalso do_lock(),
        │ │ │ +terminate(_Reason, State, _Data) ->
        │ │ │ +    State =/= locked andalso do_lock(),
        │ │ │      ok.

        │ │ │ │ │ │ │ │ │ │ │ │ Hibernation │ │ │

        │ │ │

        If you have many servers in one node and they have some state(s) in their │ │ │ @@ -1428,19 +1428,19 @@ │ │ │ footprint of a server can be minimized by hibernating it through │ │ │ proc_lib:hibernate/3.

        Note

        It is rather costly to hibernate a process; see erlang:hibernate/3. It is │ │ │ not something you want to do after every event.

        We can in this example hibernate in the {open, _} state, │ │ │ because what normally occurs in that state is that the state time-out │ │ │ after a while triggers a transition to {locked, _}:

        ...
        │ │ │  %%
        │ │ │  %% State: open
        │ │ │ -handle_event(enter, _OldState, {open,_}, _Data) ->
        │ │ │ -    do_unlock(),
        │ │ │ -    {keep_state_and_data,
        │ │ │ -     [{state_timeout,10_000,lock}, % Time in milliseconds
        │ │ │ -      hibernate]};
        │ │ │ +handle_event(enter, _OldState, {open,_}, _Data) ->
        │ │ │ +    do_unlock(),
        │ │ │ +    {keep_state_and_data,
        │ │ │ +     [{state_timeout,10_000,lock}, % Time in milliseconds
        │ │ │ +      hibernate]};
        │ │ │  ...

        The atom hibernate in the action list on the │ │ │ last line when entering the {open, _} state is the only change. If any event │ │ │ arrives in the {open, _}, state, we do not bother to rehibernate, │ │ │ so the server stays awake after any event.

        To change that we would need to insert action hibernate in more places. │ │ │ For example, the state-independent set_lock_button operation │ │ │ would have to use hibernate but only in the {open, _} state, │ │ │ which would clutter the code.

        Another not uncommon scenario is to use the │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/sup_princ.html │ │ │ @@ -128,48 +128,48 @@ │ │ │ the order specified by this list, and are terminated in the reverse order.

        │ │ │ │ │ │ │ │ │ │ │ │ Example │ │ │

        │ │ │

        The callback module for a supervisor starting the server from │ │ │ -gen_server Behaviour can look as follows:

        -module(ch_sup).
        │ │ │ --behaviour(supervisor).
        │ │ │ +gen_server Behaviour can look as follows:

        -module(ch_sup).
        │ │ │ +-behaviour(supervisor).
        │ │ │  
        │ │ │ --export([start_link/0]).
        │ │ │ --export([init/1]).
        │ │ │ +-export([start_link/0]).
        │ │ │ +-export([init/1]).
        │ │ │  
        │ │ │ -start_link() ->
        │ │ │ -    supervisor:start_link(ch_sup, []).
        │ │ │ +start_link() ->
        │ │ │ +    supervisor:start_link(ch_sup, []).
        │ │ │  
        │ │ │ -init(_Args) ->
        │ │ │ -    SupFlags = #{strategy => one_for_one, intensity => 1, period => 5},
        │ │ │ -    ChildSpecs = [#{id => ch3,
        │ │ │ -                    start => {ch3, start_link, []},
        │ │ │ +init(_Args) ->
        │ │ │ +    SupFlags = #{strategy => one_for_one, intensity => 1, period => 5},
        │ │ │ +    ChildSpecs = [#{id => ch3,
        │ │ │ +                    start => {ch3, start_link, []},
        │ │ │                      restart => permanent,
        │ │ │                      shutdown => brutal_kill,
        │ │ │                      type => worker,
        │ │ │ -                    modules => [ch3]}],
        │ │ │ -    {ok, {SupFlags, ChildSpecs}}.

        The SupFlags variable in the return value from init/1 represents the │ │ │ + modules => [ch3]}], │ │ │ + {ok, {SupFlags, ChildSpecs}}.

        The SupFlags variable in the return value from init/1 represents the │ │ │ supervisor flags.

        The ChildSpecs variable in the return value from init/1 is a list of │ │ │ child specifications.

        │ │ │ │ │ │ │ │ │ │ │ │ Supervisor Flags │ │ │

        │ │ │ -

        This is the type definition for the supervisor flags:

        sup_flags() = #{strategy => strategy(),           % optional
        │ │ │ -                intensity => non_neg_integer(),   % optional
        │ │ │ -                period => pos_integer(),          % optional
        │ │ │ -                auto_shutdown => auto_shutdown()} % optional
        │ │ │ -    strategy() = one_for_all
        │ │ │ +

        This is the type definition for the supervisor flags:

        sup_flags() = #{strategy => strategy(),           % optional
        │ │ │ +                intensity => non_neg_integer(),   % optional
        │ │ │ +                period => pos_integer(),          % optional
        │ │ │ +                auto_shutdown => auto_shutdown()} % optional
        │ │ │ +    strategy() = one_for_all
        │ │ │                 | one_for_one
        │ │ │                 | rest_for_one
        │ │ │                 | simple_one_for_one
        │ │ │ -    auto_shutdown() = never
        │ │ │ +    auto_shutdown() = never
        │ │ │                      | any_significant
        │ │ │                      | all_significant

        │ │ │ │ │ │ │ │ │ │ │ │ @@ -408,28 +408,28 @@ │ │ │ exhaust the Maximum Restart Intensity of the │ │ │ parent supervisor.

        │ │ │ │ │ │ │ │ │ │ │ │ Child Specification │ │ │

        │ │ │ -

        The type definition for a child specification is as follows:

        child_spec() = #{id => child_id(),             % mandatory
        │ │ │ -                 start => mfargs(),            % mandatory
        │ │ │ -                 restart => restart(),         % optional
        │ │ │ -                 significant => significant(), % optional
        │ │ │ -                 shutdown => shutdown(),       % optional
        │ │ │ -                 type => worker(),             % optional
        │ │ │ -                 modules => modules()}         % optional
        │ │ │ -    child_id() = term()
        │ │ │ -    mfargs() = {M :: module(), F :: atom(), A :: [term()]}
        │ │ │ -    modules() = [module()] | dynamic
        │ │ │ -    restart() = permanent | transient | temporary
        │ │ │ -    significant() = boolean()
        │ │ │ -    shutdown() = brutal_kill | timeout()
        │ │ │ -    worker() = worker | supervisor
        • id is used to identify the child specification internally by the supervisor.

          The id key is mandatory.

          Note that this identifier occasionally has been called "name". As far as │ │ │ +

          The type definition for a child specification is as follows:

          child_spec() = #{id => child_id(),             % mandatory
          │ │ │ +                 start => mfargs(),            % mandatory
          │ │ │ +                 restart => restart(),         % optional
          │ │ │ +                 significant => significant(), % optional
          │ │ │ +                 shutdown => shutdown(),       % optional
          │ │ │ +                 type => worker(),             % optional
          │ │ │ +                 modules => modules()}         % optional
          │ │ │ +    child_id() = term()
          │ │ │ +    mfargs() = {M :: module(), F :: atom(), A :: [term()]}
          │ │ │ +    modules() = [module()] | dynamic
          │ │ │ +    restart() = permanent | transient | temporary
          │ │ │ +    significant() = boolean()
          │ │ │ +    shutdown() = brutal_kill | timeout()
          │ │ │ +    worker() = worker | supervisor
          • id is used to identify the child specification internally by the supervisor.

            The id key is mandatory.

            Note that this identifier occasionally has been called "name". As far as │ │ │ possible, the terms "identifier" or "id" are now used but in order to keep │ │ │ backwards compatibility, some occurrences of "name" can still be found, for │ │ │ example in error messages.

          • start defines the function call used to start the child process. It is a │ │ │ module-function-arguments tuple used as apply(M, F, A).

            It is to be (or result in) a call to any of the following:

            The start key is mandatory.

          • restart defines when a terminated child process is to be │ │ │ restarted.

            • A permanent child process is always restarted.
            • A temporary child process is never restarted (not even when the supervisor │ │ │ restart strategy is rest_for_one or one_for_all and a sibling death │ │ │ @@ -457,53 +457,53 @@ │ │ │ supervisor, the default value infinity will be used.

            • type specifies whether the child process is a supervisor or a worker.

              The type key is optional. If it is not given, the default value worker │ │ │ will be used.

            • modules has to be a list consisting of a single element. The value │ │ │ of that element depends on the behaviour of the process:

              • If the child process is a gen_event, the element has to be the atom │ │ │ dynamic.
              • Otherwise, the element should be Module, where Module is the │ │ │ name of the callback module.

              This information is used by the release handler during upgrades and │ │ │ downgrades; see Release Handling.

              The modules key is optional. If it is not given, it defaults to [M], where │ │ │ M comes from the child's start {M,F,A}.

            Example: The child specification to start the server ch3 in the previous │ │ │ -example look as follows:

            #{id => ch3,
            │ │ │ -  start => {ch3, start_link, []},
            │ │ │ +example look as follows:

            #{id => ch3,
            │ │ │ +  start => {ch3, start_link, []},
            │ │ │    restart => permanent,
            │ │ │    shutdown => brutal_kill,
            │ │ │    type => worker,
            │ │ │ -  modules => [ch3]}

            or simplified, relying on the default values:

            #{id => ch3,
            │ │ │ +  modules => [ch3]}

            or simplified, relying on the default values:

            #{id => ch3,
            │ │ │    start => {ch3, start_link, []},
            │ │ │    shutdown => brutal_kill}

            Example: A child specification to start the event manager from the chapter about │ │ │ -gen_event:

            #{id => error_man,
            │ │ │ -  start => {gen_event, start_link, [{local, error_man}]},
            │ │ │ -  modules => dynamic}

            Both server and event manager are registered processes which can be expected to │ │ │ +gen_event:

            #{id => error_man,
            │ │ │ +  start => {gen_event, start_link, [{local, error_man}]},
            │ │ │ +  modules => dynamic}

            Both server and event manager are registered processes which can be expected to │ │ │ be always accessible. Thus they are specified to be permanent.

            ch3 does not need to do any cleaning up before termination. Thus, no shutdown │ │ │ time is needed, but brutal_kill is sufficient. error_man can need some time │ │ │ for the event handlers to clean up, thus the shutdown time is set to 5000 ms │ │ │ -(which is the default value).

            Example: A child specification to start another supervisor:

            #{id => sup,
            │ │ │ -  start => {sup, start_link, []},
            │ │ │ +(which is the default value).

            Example: A child specification to start another supervisor:

            #{id => sup,
            │ │ │ +  start => {sup, start_link, []},
            │ │ │    restart => transient,
            │ │ │ -  type => supervisor} % will cause default shutdown=>infinity

            │ │ │ + type => supervisor} % will cause default shutdown=>infinity

            │ │ │ │ │ │ │ │ │ │ │ │ Starting a Supervisor │ │ │

            │ │ │

            In the previous example, the supervisor is started by calling │ │ │ -ch_sup:start_link():

            start_link() ->
            │ │ │ -    supervisor:start_link(ch_sup, []).

            ch_sup:start_link calls function supervisor:start_link/2, which spawns and │ │ │ +ch_sup:start_link():

            start_link() ->
            │ │ │ +    supervisor:start_link(ch_sup, []).

            ch_sup:start_link calls function supervisor:start_link/2, which spawns and │ │ │ links to a new process, a supervisor.

            • The first argument, ch_sup, is the name of the callback module, that is, the │ │ │ module where the init callback function is located.
            • The second argument, [], is a term that is passed as is to the callback │ │ │ function init. Here, init does not need any data and ignores the argument.

            In this case, the supervisor is not registered. Instead its pid must be used. A │ │ │ name can be specified by calling │ │ │ supervisor:start_link({local, Name}, Module, Args) │ │ │ or │ │ │ supervisor:start_link({global, Name}, Module, Args).

            The new supervisor process calls the callback function ch_sup:init([]). init │ │ │ -has to return {ok, {SupFlags, ChildSpecs}}:

            init(_Args) ->
            │ │ │ -    SupFlags = #{},
            │ │ │ -    ChildSpecs = [#{id => ch3,
            │ │ │ -                    start => {ch3, start_link, []},
            │ │ │ -                    shutdown => brutal_kill}],
            │ │ │ -    {ok, {SupFlags, ChildSpecs}}.

            Subsequently, the supervisor starts its child processes according to the child │ │ │ +has to return {ok, {SupFlags, ChildSpecs}}:

            init(_Args) ->
            │ │ │ +    SupFlags = #{},
            │ │ │ +    ChildSpecs = [#{id => ch3,
            │ │ │ +                    start => {ch3, start_link, []},
            │ │ │ +                    shutdown => brutal_kill}],
            │ │ │ +    {ok, {SupFlags, ChildSpecs}}.

            Subsequently, the supervisor starts its child processes according to the child │ │ │ specifications in the start specification. In this case there is a single child │ │ │ process, called ch3.

            supervisor:start_link/3 is synchronous. It does not return until all child │ │ │ processes have been started.

            │ │ │ │ │ │ │ │ │ │ │ │ Adding a Child Process │ │ │ @@ -532,31 +532,31 @@ │ │ │ │ │ │ │ │ │ Simplified one_for_one Supervisors │ │ │

            │ │ │

            A supervisor with restart strategy simple_one_for_one is a simplified │ │ │ one_for_one supervisor, where all child processes are dynamically added │ │ │ instances of the same process.

            The following is an example of a callback module for a simple_one_for_one │ │ │ -supervisor:

            -module(simple_sup).
            │ │ │ --behaviour(supervisor).
            │ │ │ +supervisor:

            -module(simple_sup).
            │ │ │ +-behaviour(supervisor).
            │ │ │  
            │ │ │ --export([start_link/0]).
            │ │ │ --export([init/1]).
            │ │ │ +-export([start_link/0]).
            │ │ │ +-export([init/1]).
            │ │ │  
            │ │ │ -start_link() ->
            │ │ │ -    supervisor:start_link(simple_sup, []).
            │ │ │ +start_link() ->
            │ │ │ +    supervisor:start_link(simple_sup, []).
            │ │ │  
            │ │ │ -init(_Args) ->
            │ │ │ -    SupFlags = #{strategy => simple_one_for_one,
            │ │ │ +init(_Args) ->
            │ │ │ +    SupFlags = #{strategy => simple_one_for_one,
            │ │ │                   intensity => 0,
            │ │ │ -                 period => 1},
            │ │ │ -    ChildSpecs = [#{id => call,
            │ │ │ -                    start => {call, start_link, []},
            │ │ │ -                    shutdown => brutal_kill}],
            │ │ │ -    {ok, {SupFlags, ChildSpecs}}.

            When started, the supervisor does not start any child │ │ │ + period => 1}, │ │ │ + ChildSpecs = [#{id => call, │ │ │ + start => {call, start_link, []}, │ │ │ + shutdown => brutal_kill}], │ │ │ + {ok, {SupFlags, ChildSpecs}}.

            When started, the supervisor does not start any child │ │ │ processes. Instead, all child processes need to be added dynamically by │ │ │ calling supervisor:start_child(Sup, List).

            Sup is the pid, or name, of the supervisor. List is an arbitrary list of │ │ │ terms, which are added to the list of arguments specified in the child │ │ │ specification. If the start function is specified as {M, F, A}, the child │ │ │ process is started by calling apply(M, F, A++List).

            For example, adding a child to simple_sup above:

            supervisor:start_child(Pid, [id1])

            The result is that the child process is started by calling │ │ │ apply(call, start_link, []++[id1]), or actually:

            call:start_link(id1)

            A child under a simple_one_for_one supervisor can be terminated with the │ │ │ following:

            supervisor:terminate_child(Sup, Pid)

            Sup is the pid, or name, of the supervisor and Pid is the pid of the child.

            Because a simple_one_for_one supervisor can have many children, it shuts them │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/tablesdatabases.html │ │ │ @@ -146,73 +146,73 @@ │ │ │ │ │ │ │ │ │ Deleting an Element │ │ │

        │ │ │

        The delete operation is considered successful if the element was not present │ │ │ in the table. Hence all attempts to check that the element is present in the │ │ │ Ets/Mnesia table before deletion are unnecessary. Here follows an example for │ │ │ -Ets tables:

        DO

        ets:delete(Tab, Key),

        DO NOT

        case ets:lookup(Tab, Key) of
        │ │ │ -    [] ->
        │ │ │ +Ets tables:

        DO

        ets:delete(Tab, Key),

        DO NOT

        case ets:lookup(Tab, Key) of
        │ │ │ +    [] ->
        │ │ │          ok;
        │ │ │ -    [_|_] ->
        │ │ │ -        ets:delete(Tab, Key)
        │ │ │ +    [_|_] ->
        │ │ │ +        ets:delete(Tab, Key)
        │ │ │  end,

        │ │ │ │ │ │ │ │ │ │ │ │ Fetching Data │ │ │

        │ │ │

        Do not fetch data that you already have.

        Consider that you have a module that handles the abstract data type Person. │ │ │ You export the interface function print_person/1, which uses the internal │ │ │ functions print_name/1, print_age/1, and print_occupation/1.

        Note

        If the function print_name/1, and so on, had been interface functions, the │ │ │ situation would have been different, as you do not want the user of the │ │ │ interface to know about the internal data representation.

        DO

        %%% Interface function
        │ │ │ -print_person(PersonId) ->
        │ │ │ +print_person(PersonId) ->
        │ │ │      %% Look up the person in the named table person,
        │ │ │ -    case ets:lookup(person, PersonId) of
        │ │ │ -        [Person] ->
        │ │ │ -            print_name(Person),
        │ │ │ -            print_age(Person),
        │ │ │ -            print_occupation(Person);
        │ │ │ -        [] ->
        │ │ │ -            io:format("No person with ID = ~p~n", [PersonID])
        │ │ │ +    case ets:lookup(person, PersonId) of
        │ │ │ +        [Person] ->
        │ │ │ +            print_name(Person),
        │ │ │ +            print_age(Person),
        │ │ │ +            print_occupation(Person);
        │ │ │ +        [] ->
        │ │ │ +            io:format("No person with ID = ~p~n", [PersonID])
        │ │ │      end.
        │ │ │  
        │ │ │  %%% Internal functions
        │ │ │ -print_name(Person) ->
        │ │ │ -    io:format("No person ~p~n", [Person#person.name]).
        │ │ │ +print_name(Person) ->
        │ │ │ +    io:format("No person ~p~n", [Person#person.name]).
        │ │ │  
        │ │ │ -print_age(Person) ->
        │ │ │ -    io:format("No person ~p~n", [Person#person.age]).
        │ │ │ +print_age(Person) ->
        │ │ │ +    io:format("No person ~p~n", [Person#person.age]).
        │ │ │  
        │ │ │ -print_occupation(Person) ->
        │ │ │ -    io:format("No person ~p~n", [Person#person.occupation]).

        DO NOT

        %%% Interface function
        │ │ │ -print_person(PersonId) ->
        │ │ │ +print_occupation(Person) ->
        │ │ │ +    io:format("No person ~p~n", [Person#person.occupation]).

        DO NOT

        %%% Interface function
        │ │ │ +print_person(PersonId) ->
        │ │ │      %% Look up the person in the named table person,
        │ │ │ -    case ets:lookup(person, PersonId) of
        │ │ │ -        [Person] ->
        │ │ │ -            print_name(PersonID),
        │ │ │ -            print_age(PersonID),
        │ │ │ -            print_occupation(PersonID);
        │ │ │ -        [] ->
        │ │ │ -            io:format("No person with ID = ~p~n", [PersonID])
        │ │ │ +    case ets:lookup(person, PersonId) of
        │ │ │ +        [Person] ->
        │ │ │ +            print_name(PersonID),
        │ │ │ +            print_age(PersonID),
        │ │ │ +            print_occupation(PersonID);
        │ │ │ +        [] ->
        │ │ │ +            io:format("No person with ID = ~p~n", [PersonID])
        │ │ │      end.
        │ │ │  
        │ │ │  %%% Internal functions
        │ │ │ -print_name(PersonID) ->
        │ │ │ -    [Person] = ets:lookup(person, PersonId),
        │ │ │ -    io:format("No person ~p~n", [Person#person.name]).
        │ │ │ -
        │ │ │ -print_age(PersonID) ->
        │ │ │ -    [Person] = ets:lookup(person, PersonId),
        │ │ │ -    io:format("No person ~p~n", [Person#person.age]).
        │ │ │ -
        │ │ │ -print_occupation(PersonID) ->
        │ │ │ -    [Person] = ets:lookup(person, PersonId),
        │ │ │ -    io:format("No person ~p~n", [Person#person.occupation]).

        │ │ │ +print_name(PersonID) -> │ │ │ + [Person] = ets:lookup(person, PersonId), │ │ │ + io:format("No person ~p~n", [Person#person.name]). │ │ │ + │ │ │ +print_age(PersonID) -> │ │ │ + [Person] = ets:lookup(person, PersonId), │ │ │ + io:format("No person ~p~n", [Person#person.age]). │ │ │ + │ │ │ +print_occupation(PersonID) -> │ │ │ + [Person] = ets:lookup(person, PersonId), │ │ │ + io:format("No person ~p~n", [Person#person.occupation]).

        │ │ │ │ │ │ │ │ │ │ │ │ Non-Persistent Database Storage │ │ │

        │ │ │

        For non-persistent database storage, prefer Ets tables over Mnesia │ │ │ local_content tables. Even the Mnesia dirty_write operations carry a fixed │ │ │ @@ -226,38 +226,38 @@ │ │ │ │ │ │

        Assuming an Ets table that uses idno as key and contains the following:

        [#person{idno = 1, name = "Adam",  age = 31, occupation = "mailman"},
        │ │ │   #person{idno = 2, name = "Bryan", age = 31, occupation = "cashier"},
        │ │ │   #person{idno = 3, name = "Bryan", age = 35, occupation = "banker"},
        │ │ │   #person{idno = 4, name = "Carl",  age = 25, occupation = "mailman"}]

        If you must return all data stored in the Ets table, you can use │ │ │ ets:tab2list/1. However, usually you are only interested in a subset of the │ │ │ information in which case ets:tab2list/1 is expensive. If you only want to │ │ │ -extract one field from each record, for example, the age of every person, then:

        DO

        ets:select(Tab, [{#person{idno='_',
        │ │ │ +extract one field from each record, for example, the age of every person, then:

        DO

        ets:select(Tab, [{#person{idno='_',
        │ │ │                            name='_',
        │ │ │                            age='$1',
        │ │ │ -                          occupation = '_'},
        │ │ │ -                [],
        │ │ │ -                ['$1']}]),

        DO NOT

        TabList = ets:tab2list(Tab),
        │ │ │ -lists:map(fun(X) -> X#person.age end, TabList),

        If you are only interested in the age of all persons named "Bryan", then:

        DO

        ets:select(Tab, [{#person{idno='_',
        │ │ │ +                          occupation = '_'},
        │ │ │ +                [],
        │ │ │ +                ['$1']}]),

        DO NOT

        TabList = ets:tab2list(Tab),
        │ │ │ +lists:map(fun(X) -> X#person.age end, TabList),

        If you are only interested in the age of all persons named "Bryan", then:

        DO

        ets:select(Tab, [{#person{idno='_',
        │ │ │                            name="Bryan",
        │ │ │                            age='$1',
        │ │ │ -                          occupation = '_'},
        │ │ │ -                [],
        │ │ │ -                ['$1']}])

        DO NOT

        TabList = ets:tab2list(Tab),
        │ │ │ -lists:foldl(fun(X, Acc) -> case X#person.name of
        │ │ │ +                          occupation = '_'},
        │ │ │ +                [],
        │ │ │ +                ['$1']}])

        DO NOT

        TabList = ets:tab2list(Tab),
        │ │ │ +lists:foldl(fun(X, Acc) -> case X#person.name of
        │ │ │                                  "Bryan" ->
        │ │ │ -                                    [X#person.age|Acc];
        │ │ │ +                                    [X#person.age|Acc];
        │ │ │                                   _ ->
        │ │ │                                       Acc
        │ │ │                             end
        │ │ │ -             end, [], TabList)

        If you need all information stored in the Ets table about persons named "Bryan", │ │ │ -then:

        DO

        ets:select(Tab, [{#person{idno='_',
        │ │ │ +             end, [], TabList)

        If you need all information stored in the Ets table about persons named "Bryan", │ │ │ +then:

        DO

        ets:select(Tab, [{#person{idno='_',
        │ │ │                            name="Bryan",
        │ │ │                            age='_',
        │ │ │ -                          occupation = '_'}, [], ['$_']}]),

        DO NOT

        TabList = ets:tab2list(Tab),
        │ │ │ -lists:filter(fun(X) -> X#person.name == "Bryan" end, TabList),

        │ │ │ + occupation = '_'}, [], ['$_']}]),

        DO NOT

        TabList = ets:tab2list(Tab),
        │ │ │ +lists:filter(fun(X) -> X#person.name == "Bryan" end, TabList),

        │ │ │ │ │ │ │ │ │ │ │ │ ordered_set Tables │ │ │

        │ │ │

        If the data in the table is to be accessed so that the order of the keys in the │ │ │ table is significant, the table type ordered_set can be used instead of the │ │ │ @@ -293,20 +293,20 @@ │ │ │ Clearly, the second table would have to be kept consistent with the master │ │ │ table. Mnesia can do this for you, but a home-brew index table can be very │ │ │ efficient compared to the overhead involved in using Mnesia.

        An index table for the table in the previous examples would have to be a bag (as │ │ │ keys would appear more than once) and can have the following contents:

        [#index_entry{name="Adam", idno=1},
        │ │ │   #index_entry{name="Bryan", idno=2},
        │ │ │   #index_entry{name="Bryan", idno=3},
        │ │ │   #index_entry{name="Carl", idno=4}]

        Given this index table, a lookup of the age fields for all persons named │ │ │ -"Bryan" can be done as follows:

        MatchingIDs = ets:lookup(IndexTable,"Bryan"),
        │ │ │ -lists:map(fun(#index_entry{idno = ID}) ->
        │ │ │ -                 [#person{age = Age}] = ets:lookup(PersonTable, ID),
        │ │ │ +"Bryan" can be done as follows:

        MatchingIDs = ets:lookup(IndexTable,"Bryan"),
        │ │ │ +lists:map(fun(#index_entry{idno = ID}) ->
        │ │ │ +                 [#person{age = Age}] = ets:lookup(PersonTable, ID),
        │ │ │                   Age
        │ │ │            end,
        │ │ │ -          MatchingIDs),

        Notice that this code does not use ets:match/2, but instead uses the │ │ │ + MatchingIDs),

        Notice that this code does not use ets:match/2, but instead uses the │ │ │ ets:lookup/2 call. The lists:map/2 call is only used to traverse the idnos │ │ │ matching the name "Bryan" in the table; thus the number of lookups in the master │ │ │ table is minimized.

        Keeping an index table introduces some overhead when inserting records in the │ │ │ table. The number of operations gained from the table must therefore be compared │ │ │ against the number of operations inserting objects in the table. However, notice │ │ │ that the gain is significant when the key can be used to lookup elements.

        │ │ │ │ │ │ @@ -321,51 +321,51 @@ │ │ │ Secondary Index │ │ │

        │ │ │

        If you frequently do lookups on a field that is not the key of the table, you │ │ │ lose performance using mnesia:select() or │ │ │ mnesia:match_object() as these function traverse │ │ │ the whole table. Instead, you can create a secondary index and use │ │ │ mnesia:index_read/3 to get faster access at the expense of using more │ │ │ -memory.

        Example:

        -record(person, {idno, name, age, occupation}).
        │ │ │ +memory.

        Example:

        -record(person, {idno, name, age, occupation}).
        │ │ │          ...
        │ │ │ -{atomic, ok} =
        │ │ │ -mnesia:create_table(person, [{index,[#person.age]},
        │ │ │ -                              {attributes,
        │ │ │ -                                    record_info(fields, person)}]),
        │ │ │ -{atomic, ok} = mnesia:add_table_index(person, age),
        │ │ │ +{atomic, ok} =
        │ │ │ +mnesia:create_table(person, [{index,[#person.age]},
        │ │ │ +                              {attributes,
        │ │ │ +                                    record_info(fields, person)}]),
        │ │ │ +{atomic, ok} = mnesia:add_table_index(person, age),
        │ │ │  ...
        │ │ │  
        │ │ │  PersonsAge42 =
        │ │ │ -     mnesia:dirty_index_read(person, 42, #person.age),

        │ │ │ + mnesia:dirty_index_read(person, 42, #person.age),

        │ │ │ │ │ │ │ │ │ │ │ │ Transactions │ │ │

        │ │ │

        Using transactions is a way to guarantee that the distributed Mnesia database │ │ │ remains consistent, even when many different processes update it in parallel. │ │ │ However, if you have real-time requirements it is recommended to use dirtry │ │ │ operations instead of transactions. When using dirty operations, you lose the │ │ │ consistency guarantee; this is usually solved by only letting one process update │ │ │ the table. Other processes must send update requests to that process.

        Example:

        ...
        │ │ │  %% Using transaction
        │ │ │  
        │ │ │ -Fun = fun() ->
        │ │ │ -          [mnesia:read({Table, Key}),
        │ │ │ -           mnesia:read({Table2, Key2})]
        │ │ │ +Fun = fun() ->
        │ │ │ +          [mnesia:read({Table, Key}),
        │ │ │ +           mnesia:read({Table2, Key2})]
        │ │ │        end,
        │ │ │  
        │ │ │ -{atomic, [Result1, Result2]}  = mnesia:transaction(Fun),
        │ │ │ +{atomic, [Result1, Result2]}  = mnesia:transaction(Fun),
        │ │ │  ...
        │ │ │  
        │ │ │  %% Same thing using dirty operations
        │ │ │  ...
        │ │ │  
        │ │ │ -Result1 = mnesia:dirty_read({Table, Key}),
        │ │ │ -Result2 = mnesia:dirty_read({Table2, Key2}),
        │ │ │ +
        Result1 = mnesia:dirty_read({Table, Key}), │ │ │ +Result2 = mnesia:dirty_read({Table2, Key2}),
        │ │ │ │ │ │ │ │ │
        │ │ │
        │ │ │ │ │ │

        map/0 type.

        For convenience, the following types are also built-in. They can be thought as │ │ │ predefined aliases for the type unions also shown in the table.

        Built-in typeDefined as
        term/0any/0
        binary/0<<_:_*8>>
        nonempty_binary/0<<_:8, _:_*8>>
        bitstring/0<<_:_*1>>
        nonempty_bitstring/0<<_:1, _:_*1>>
        boolean/0'false' | 'true'
        byte/00..255
        char/00..16#10ffff
        nil/0[]
        number/0integer/0 | float/0
        list/0[any()]
        maybe_improper_list/0maybe_improper_list(any(), any())
        nonempty_list/0nonempty_list(any())
        string/0[char()]
        nonempty_string/0[char(), ...]
        iodata/0iolist() | binary()
        iolist/0maybe_improper_list(byte() | binary() | iolist(), binary() | [])
        map/0#{any() => any()}
        function/0fun()
        module/0atom/0
        mfa/0{module(),atom(),arity()}
        arity/00..255
        identifier/0pid() | port() | reference()
        node/0atom/0
        timeout/0'infinity' | non_neg_integer()
        no_return/0none/0

        Table: Built-in types, predefined aliases

        In addition, the following three built-in types exist and can be thought as │ │ │ defined below, though strictly their "type definition" is not valid syntax │ │ │ according to the type language defined above.

        Built-in typeCan be thought defined by the syntax
        non_neg_integer/00..
        pos_integer/01..
        neg_integer/0..-1

        Table: Additional built-in types

        Note

        The following built-in list types also exist, but they are expected to be │ │ │ -rarely used. Hence, they have long names:

        nonempty_maybe_improper_list() :: nonempty_maybe_improper_list(any(), any())
        │ │ │ -nonempty_improper_list(Type1, Type2)
        │ │ │ -nonempty_maybe_improper_list(Type1, Type2)

        where the last two types define the set of Erlang terms one would expect.

        Also for convenience, record notation is allowed to be used. Records are │ │ │ -shorthands for the corresponding tuples:

        Record :: #Erlang_Atom{}
        │ │ │ -        | #Erlang_Atom{Fields}

        Records are extended to possibly contain type information. This is described in │ │ │ +rarely used. Hence, they have long names:

        nonempty_maybe_improper_list() :: nonempty_maybe_improper_list(any(), any())
        │ │ │ +nonempty_improper_list(Type1, Type2)
        │ │ │ +nonempty_maybe_improper_list(Type1, Type2)

        where the last two types define the set of Erlang terms one would expect.

        Also for convenience, record notation is allowed to be used. Records are │ │ │ +shorthands for the corresponding tuples:

        Record :: #Erlang_Atom{}
        │ │ │ +        | #Erlang_Atom{Fields}

        Records are extended to possibly contain type information. This is described in │ │ │ Type Information in Record Declarations.

        │ │ │ │ │ │ │ │ │ │ │ │ Redefining built-in types │ │ │

        │ │ │

        Change

        Starting from Erlang/OTP 26, it is permitted to define a type having the same │ │ │ name as a built-in type.

        It is recommended to avoid deliberately reusing built-in names because it can be │ │ │ confusing. However, when an Erlang/OTP release introduces a new type, code that │ │ │ happened to define its own type having the same name will continue to work.

        As an example, imagine that the Erlang/OTP 42 release introduces a new type │ │ │ -gadget() defined like this:

        -type gadget() :: {'gadget', reference()}.

        Further imagine that some code has its own (different) definition of gadget(), │ │ │ -for example:

        -type gadget() :: #{}.

        Since redefinitions are allowed, the code will still compile (but with a │ │ │ +gadget() defined like this:

        -type gadget() :: {'gadget', reference()}.

        Further imagine that some code has its own (different) definition of gadget(), │ │ │ +for example:

        -type gadget() :: #{}.

        Since redefinitions are allowed, the code will still compile (but with a │ │ │ warning), and Dialyzer will not emit any additional warnings.

        │ │ │ │ │ │ │ │ │ │ │ │ Type Declarations of User-Defined Types │ │ │

        │ │ │

        As seen, the basic syntax of a type is an atom followed by closed parentheses. │ │ │ New types are declared using -type, -opaque, and │ │ │ --nominal attributes as in the following example:

        -type my_struct_type() :: Type.
        │ │ │ --opaque my_opaq_type() :: Type.
        │ │ │ --nominal my_nominal_type() :: Type.

        The type name is the atom my_struct_type, followed by parentheses. Type is a │ │ │ +-nominal attributes as in the following example:

        -type my_struct_type() :: Type.
        │ │ │ +-opaque my_opaq_type() :: Type.
        │ │ │ +-nominal my_nominal_type() :: Type.

        The type name is the atom my_struct_type, followed by parentheses. Type is a │ │ │ type as defined in the previous section. A current restriction is that Type │ │ │ can contain only predefined types, or user-defined types which are either of the │ │ │ following:

        • Module-local type, that is, with a definition that is present in the code of │ │ │ the module
        • Remote type, that is, type defined in, and exported by, other modules; more │ │ │ about this soon.

        For module-local types, the restriction that their definition exists in the │ │ │ module is enforced by the compiler and results in a compilation error. (A │ │ │ similar restriction currently exists for records.)

        Type declarations can also be parameterized by including type variables between │ │ │ the parentheses. The syntax of type variables is the same as Erlang variables, │ │ │ that is, starts with an upper-case letter. These variables is to │ │ │ -appear on the RHS of the definition. A concrete example follows:

        -type orddict(Key, Val) :: [{Key, Val}].

        A module can export some types to declare that other modules are allowed to │ │ │ -refer to them as remote types. This declaration has the following form:

        -export_type([T1/A1, ..., Tk/Ak]).

        Here the Tis are atoms (the name of the type) and the Ais are their arguments.

        Example:

        -export_type([my_struct_type/0, orddict/2]).

        Assuming that these types are exported from module 'mod', you can refer to │ │ │ -them from other modules using remote type expressions like the following:

        mod:my_struct_type()
        │ │ │ -mod:orddict(atom(), term())

        It is not allowed to refer to types that are not declared as exported.

        Types declared as opaque represent sets of terms whose structure is not │ │ │ +appear on the RHS of the definition. A concrete example follows:

        -type orddict(Key, Val) :: [{Key, Val}].

        A module can export some types to declare that other modules are allowed to │ │ │ +refer to them as remote types. This declaration has the following form:

        -export_type([T1/A1, ..., Tk/Ak]).

        Here the Tis are atoms (the name of the type) and the Ais are their arguments.

        Example:

        -export_type([my_struct_type/0, orddict/2]).

        Assuming that these types are exported from module 'mod', you can refer to │ │ │ +them from other modules using remote type expressions like the following:

        mod:my_struct_type()
        │ │ │ +mod:orddict(atom(), term())

        It is not allowed to refer to types that are not declared as exported.

        Types declared as opaque represent sets of terms whose structure is not │ │ │ supposed to be visible from outside of their defining module. That is, only the │ │ │ module defining them is allowed to depend on their term structure. Consequently, │ │ │ such types do not make much sense as module local - module local types are not │ │ │ accessible by other modules anyway - and is always to be exported.

        Change

        Nominal types were introduced in Erlang/OTP 28.

        Types declared as nominal are type-checked according to the user-defined │ │ │ names instead of their structure. That is, -nominal feet() :: integer() and │ │ │ -nominal meter() :: integer() are not the same type, while if -type is │ │ │ used it would be.

        Read more on Opaques and Nominals

        │ │ │ │ │ │ │ │ │ │ │ │ Type Information in Record Declarations │ │ │

        │ │ │

        The types of record fields can be specified in the declaration of the record. │ │ │ -The syntax for this is as follows:

        -record(rec, {field1 :: Type1, field2, field3 :: Type3}).

        For fields without type annotations, their type defaults to any(). That is, the │ │ │ -previous example is a shorthand for the following:

        -record(rec, {field1 :: Type1, field2 :: any(), field3 :: Type3}).

        In the presence of initial values for fields, the type must be declared after │ │ │ -the initialization, as follows:

        -record(rec, {field1 = [] :: Type1, field2, field3 = 42 :: Type3}).

        The initial values for fields are to be compatible with (that is, a member of) │ │ │ +The syntax for this is as follows:

        -record(rec, {field1 :: Type1, field2, field3 :: Type3}).

        For fields without type annotations, their type defaults to any(). That is, the │ │ │ +previous example is a shorthand for the following:

        -record(rec, {field1 :: Type1, field2 :: any(), field3 :: Type3}).

        In the presence of initial values for fields, the type must be declared after │ │ │ +the initialization, as follows:

        -record(rec, {field1 = [] :: Type1, field2, field3 = 42 :: Type3}).

        The initial values for fields are to be compatible with (that is, a member of) │ │ │ the corresponding types. This is checked by the compiler and results in a │ │ │ compilation error if a violation is detected.

        Change

        Before Erlang/OTP 19, for fields without initial values, the singleton type │ │ │ 'undefined' was added to all declared types. In other words, the following │ │ │ -two record declarations had identical effects:

        -record(rec, {f1 = 42 :: integer(),
        │ │ │ -             f2      :: float(),
        │ │ │ -             f3      :: 'a' | 'b'}).
        │ │ │ +two record declarations had identical effects:

        -record(rec, {f1 = 42 :: integer(),
        │ │ │ +             f2      :: float(),
        │ │ │ +             f3      :: 'a' | 'b'}).
        │ │ │  
        │ │ │ --record(rec, {f1 = 42 :: integer(),
        │ │ │ -              f2      :: 'undefined' | float(),
        │ │ │ -              f3      :: 'undefined' | 'a' | 'b'}).

        This is no longer the case. If you require 'undefined' in your record field │ │ │ +-record(rec, {f1 = 42 :: integer(), │ │ │ + f2 :: 'undefined' | float(), │ │ │ + f3 :: 'undefined' | 'a' | 'b'}).

        This is no longer the case. If you require 'undefined' in your record field │ │ │ type, you must explicitly add it to the typespec, as in the 2nd example.

        Any record, containing type information or not, once defined, can be used as a │ │ │ type using the following syntax:

        #rec{}

        In addition, the record fields can be further specified when using a record type │ │ │ by adding type information about the field as follows:

        #rec{some_field :: Type}

        Any unspecified fields are assumed to have the type in the original record │ │ │ declaration.

        Note

        When records are used to create patterns for ETS and Mnesia match functions, │ │ │ -Dialyzer may need some help not to emit bad warnings. For example:

        -type height() :: pos_integer().
        │ │ │ --record(person, {name :: string(), height :: height()}).
        │ │ │ +Dialyzer may need some help not to emit bad warnings. For example:

        -type height() :: pos_integer().
        │ │ │ +-record(person, {name :: string(), height :: height()}).
        │ │ │  
        │ │ │ -lookup(Name, Tab) ->
        │ │ │ -    ets:match_object(Tab, #person{name = Name, _ = '_'}).

        Dialyzer will emit a warning since '_' is not in the type of record field │ │ │ +lookup(Name, Tab) -> │ │ │ + ets:match_object(Tab, #person{name = Name, _ = '_'}).

        Dialyzer will emit a warning since '_' is not in the type of record field │ │ │ height.

        The recommended way of dealing with this is to declare the smallest record │ │ │ field types to accommodate all your needs, and then create refinements as │ │ │ -needed. The modified example:

        -record(person, {name :: string(), height :: height() | '_'}).
        │ │ │ +needed. The modified example:

        -record(person, {name :: string(), height :: height() | '_'}).
        │ │ │  
        │ │ │ --type person() :: #person{height :: height()}.

        In specifications and type declarations the type person() is to be preferred │ │ │ +-type person() :: #person{height :: height()}.

        In specifications and type declarations the type person() is to be preferred │ │ │ before #person{}.

        │ │ │ │ │ │ │ │ │ │ │ │ Specifications for Functions │ │ │

        │ │ │

        A specification (or contract) for a function is given using the -spec │ │ │ attribute. The general format is as follows:

        -spec Function(ArgType1, ..., ArgTypeN) -> ReturnType.

        An implementation of the function with the same name Function must exist in │ │ │ the current module, and the arity of the function must match the number of │ │ │ arguments, otherwise the compilation fails.

        The following longer format with module name is also valid as long as Module │ │ │ is the name of the current module. This can be useful for documentation │ │ │ purposes.

        -spec Module:Function(ArgType1, ..., ArgTypeN) -> ReturnType.

        Also, for documentation purposes, argument names can be given:

        -spec Function(ArgName1 :: Type1, ..., ArgNameN :: TypeN) -> RT.

        A function specification can be overloaded. That is, it can have several types, │ │ │ -separated by a semicolon (;). For example:

        -spec foo(T1, T2) -> T3;
        │ │ │ -         (T4, T5) -> T6.

        A current restriction, which currently results in a warning by Dialyzer, is that │ │ │ +separated by a semicolon (;). For example:

        -spec foo(T1, T2) -> T3;
        │ │ │ +         (T4, T5) -> T6.

        A current restriction, which currently results in a warning by Dialyzer, is that │ │ │ the domains of the argument types cannot overlap. For example, the following │ │ │ -specification results in a warning:

        -spec foo(pos_integer()) -> pos_integer();
        │ │ │ -         (integer()) -> integer().

        Type variables can be used in specifications to specify relations for the input │ │ │ +specification results in a warning:

        -spec foo(pos_integer()) -> pos_integer();
        │ │ │ +         (integer()) -> integer().

        Type variables can be used in specifications to specify relations for the input │ │ │ and output arguments of a function. For example, the following specification │ │ │ defines the type of a polymorphic identity function:

        -spec id(X) -> X.

        Notice that the above specification does not restrict the input and output type │ │ │ in any way. These types can be constrained by guard-like subtype constraints and │ │ │ -provide bounded quantification:

        -spec id(X) -> X when X :: tuple().

        Currently, the :: constraint (read as "is a subtype of") is the only guard │ │ │ +provide bounded quantification:

        -spec id(X) -> X when X :: tuple().

        Currently, the :: constraint (read as "is a subtype of") is the only guard │ │ │ constraint that can be used in the when part of a -spec attribute.

        Note

        The above function specification uses multiple occurrences of the same type │ │ │ variable. That provides more type information than the following function │ │ │ -specification, where the type variables are missing:

        -spec id(tuple()) -> tuple().

        The latter specification says that the function takes some tuple and returns │ │ │ +specification, where the type variables are missing:

        -spec id(tuple()) -> tuple().

        The latter specification says that the function takes some tuple and returns │ │ │ some tuple. The specification with the X type variable specifies that the │ │ │ function takes a tuple and returns the same tuple.

        However, it is up to the tools that process the specifications to choose │ │ │ whether to take this extra information into account or not.

        The scope of a :: constraint is the (...) -> RetType specification after │ │ │ which it appears. To avoid confusion, it is suggested that different variables │ │ │ are used in different constituents of an overloaded contract, as shown in the │ │ │ -following example:

        -spec foo({X, integer()}) -> X when X :: atom();
        │ │ │ -         ([Y]) -> Y when Y :: number().

        Some functions in Erlang are not meant to return; either because they define │ │ │ +following example:

        -spec foo({X, integer()}) -> X when X :: atom();
        │ │ │ +         ([Y]) -> Y when Y :: number().

        Some functions in Erlang are not meant to return; either because they define │ │ │ servers or because they are used to throw exceptions, as in the following │ │ │ -function:

        my_error(Err) -> throw({error, Err}).

        For such functions, it is recommended to use the special no_return/0 type │ │ │ +function:

        my_error(Err) -> throw({error, Err}).

        For such functions, it is recommended to use the special no_return/0 type │ │ │ for their "return", through a contract of the following form:

        -spec my_error(term()) -> no_return().

        Note

        Erlang uses the shorthand version _ as an anonymous type variable equivalent │ │ │ to term/0 or any/0. For example, the following function

        -spec Function(string(), _) -> string().

        is equivalent to:

        -spec Function(string(), any()) -> string().
        │ │ │
        │ │ │ │ │ │
        │ │ │
        │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/vulnerabilities.html │ │ │ @@ -143,69 +143,69 @@ │ │ │ vulnerability (affected), the affected Erlang/OTP releases, namely 28.0, │ │ │ 28.0.1, and 28.0.2, and the Erlang/OTP application that was vulnerable │ │ │ in application version ssh@5.3, ssh@5.3.1, and ssh@5.3.2. │ │ │ Erlang/OTP reports the affected versions using the release and the │ │ │ application versions because it is possible to update the application independently │ │ │ from the release. │ │ │ In some cases, there may be an optional action statement that describes a workaround │ │ │ -to avoid the mentioned vulnerability.

        {
        │ │ │ -  "vulnerability": {
        │ │ │ +to avoid the mentioned vulnerability.

        {
        │ │ │ +  "vulnerability": {
        │ │ │      "name": "CVE-2025-48038"
        │ │ │ -  },
        │ │ │ +  },
        │ │ │    "timestamp": "2025-09-16T08:22:13.223967395Z",
        │ │ │ -  "products": [
        │ │ │ -    { "@id": "pkg:github/erlang/otp@OTP-28.0" },
        │ │ │ -    { "@id": "pkg:github/erlang/otp@OTP-28.0.1" },
        │ │ │ -    { "@id": "pkg:github/erlang/otp@OTP-28.0.2" },
        │ │ │ -    { "@id": "pkg:otp/ssh@5.3" },
        │ │ │ -    { "@id": "pkg:otp/ssh@5.3.1" },
        │ │ │ -    { "@id": "pkg:otp/ssh@5.3.2" }
        │ │ │ -  ],
        │ │ │ +  "products": [
        │ │ │ +    { "@id": "pkg:github/erlang/otp@OTP-28.0" },
        │ │ │ +    { "@id": "pkg:github/erlang/otp@OTP-28.0.1" },
        │ │ │ +    { "@id": "pkg:github/erlang/otp@OTP-28.0.2" },
        │ │ │ +    { "@id": "pkg:otp/ssh@5.3" },
        │ │ │ +    { "@id": "pkg:otp/ssh@5.3.1" },
        │ │ │ +    { "@id": "pkg:otp/ssh@5.3.2" }
        │ │ │ +  ],
        │ │ │    "status": "affected",
        │ │ │    "action_statement": "Update to any of the following versions: pkg:otp/ssh@5.3.3",
        │ │ │    "action_statement_timestamp": "2025-09-16T08:22:13.223967395Z"
        │ │ │ -},

        Erlang/OTP reports the fixed version in a similar fashion as follows, in the same document. │ │ │ +},

        Erlang/OTP reports the fixed version in a similar fashion as follows, in the same document. │ │ │ As an example, there is a new statement for CVE-2025-48038 with status fixed, │ │ │ that links to the first release that do not suffer from CVE-2025-48038, namely │ │ │ -OTP version 28.0.3 and application ssh@5.3.3.

        {
        │ │ │ -  "vulnerability": {
        │ │ │ +OTP version 28.0.3 and application ssh@5.3.3. 

        {
        │ │ │ +  "vulnerability": {
        │ │ │      "name": "CVE-2025-48038"
        │ │ │ -  },
        │ │ │ +  },
        │ │ │    "timestamp": "2025-09-16T08:22:13.241103494Z",
        │ │ │ -  "products": [
        │ │ │ -    { "@id": "pkg:github/erlang/otp@OTP-28.0.4" },
        │ │ │ -    { "@id": "pkg:github/erlang/otp@OTP-28.0.3" },
        │ │ │ -    { "@id": "pkg:otp/ssh@5.3.3" }
        │ │ │ -  ],
        │ │ │ +  "products": [
        │ │ │ +    { "@id": "pkg:github/erlang/otp@OTP-28.0.4" },
        │ │ │ +    { "@id": "pkg:github/erlang/otp@OTP-28.0.3" },
        │ │ │ +    { "@id": "pkg:otp/ssh@5.3.3" }
        │ │ │ +  ],
        │ │ │    "status": "fixed"
        │ │ │ -},

        │ │ │ +},

        │ │ │ │ │ │ │ │ │ │ │ │ Third Party VEX Statements │ │ │

        │ │ │

        Erlang/OTP generates statements for third parties from which the project depends │ │ │ on. It is really important to understand the scope of the third party │ │ │ applications, since Erlang/OTP vendors some libraries as part of the runtime.

        Vendoring means that Erlang/OTP code contains a local copy of a library. │ │ │ There are numerous use cases for why this is necessary, and we will not cover the use cases here.

        This excludes dynamically or statically linked libraries during the Erlang/OTP build process. For instance, any security related Erlang application will rely on dynamically or statically linked version of OpenSSL cryptolib.

        Erlang/OTP reports vulnerabilities for any source code that is vulnerable and │ │ │ included in the Erlang/OTP release.

        The OpenVEX statements for our third party libraries specify the affected/fixed │ │ │ version using the commit SHA1 from their respective repository. This is simply │ │ │ because our third party dependencies are in C/C++ and vulnerability scanners │ │ │ such as OSV report vulnerabilities in SHA1 ranges.

        As an example, we mention that the OpenSSL code that Erlang/OTP vendors │ │ │ -is not susceptible for CVE-2023-6129, as follows:

        {
        │ │ │ -  "vulnerability": {
        │ │ │ +is not susceptible for CVE-2023-6129, as follows:

        {
        │ │ │ +  "vulnerability": {
        │ │ │      "name": "CVE-2023-6129"
        │ │ │ -  },
        │ │ │ +  },
        │ │ │    "timestamp": "2025-06-18T12:18:16.47247833+02:00",
        │ │ │ -  "products": [
        │ │ │ -     { "@id": "pkg:github/openssl/openssl@01d5e2318405362b4de5e670c90d9b40a351d053" }
        │ │ │ -  ],
        │ │ │ +  "products": [
        │ │ │ +     { "@id": "pkg:github/openssl/openssl@01d5e2318405362b4de5e670c90d9b40a351d053" }
        │ │ │ +  ],
        │ │ │    "status": "not_affected",
        │ │ │    "justification": "vulnerable_code_not_present"
        │ │ │ -}

        Diving into the example, this means that Erlang/OTP vendors a version of openssl taken from commit 01d5e2318405362b4de5e670c90d9b40a351d053 from the repository https://github.com/openssl/openssl/commit/01d5e2318405362b4de5e670c90d9b40a351d053 (version of OpenSSL 3.1.4). The openssl code that Erlang/OTP vendors can be found in ./lib/erl_interface/src/openssl/ and ./erts/emulator/openssl/. The OpenVEX statement claims that the code in those folders is not susceptible to CVE-2023-6129. The claim is towards source code existing in Erlang/OTP.

        In other words, the not_affected status refers to the library that Erlang/OTP vendors for OpenSSL (the library that comes │ │ │ +}

        Diving into the example, this means that Erlang/OTP vendors a version of openssl taken from commit 01d5e2318405362b4de5e670c90d9b40a351d053 from the repository https://github.com/openssl/openssl/commit/01d5e2318405362b4de5e670c90d9b40a351d053 (version of OpenSSL 3.1.4). The openssl code that Erlang/OTP vendors can be found in ./lib/erl_interface/src/openssl/ and ./erts/emulator/openssl/. The OpenVEX statement claims that the code in those folders is not susceptible to CVE-2023-6129. The claim is towards source code existing in Erlang/OTP.

        In other words, the not_affected status refers to the library that Erlang/OTP vendors for OpenSSL (the library that comes │ │ │ included with Erlang/OTP). If you build Erlang/OTP and link to any OpenSSL version (e.g., 3.5.2 or even 3.1.4) during the building process, │ │ │ your project has now a new build and runtime dependency and may be subject to CVE-2023-6129.

        │ │ │ │ │ │ │ │ │ │ │ │ Windows Binaries │ │ │

        │ │ ├── ./usr/share/doc/erlang-doc/html/doc/upcoming_incompatibilities.html │ │ │ @@ -149,45 +149,45 @@ │ │ │ occurrences of maybe without quotes.

        │ │ │ │ │ │ │ │ │ │ │ │ 0.0 and -0.0 will no longer be exactly equal │ │ │

        │ │ │

        Currently, the floating point numbers 0.0 and -0.0 have distinct internal │ │ │ -representations. That can be seen if they are converted to binaries:

        1> <<0.0/float>>.
        │ │ │ -<<0,0,0,0,0,0,0,0>>
        │ │ │ -2> <<-0.0/float>>.
        │ │ │ -<<128,0,0,0,0,0,0,0>>

        However, when they are matched against each other or compared using the =:= │ │ │ +representations. That can be seen if they are converted to binaries:

        1> <<0.0/float>>.
        │ │ │ +<<0,0,0,0,0,0,0,0>>
        │ │ │ +2> <<-0.0/float>>.
        │ │ │ +<<128,0,0,0,0,0,0,0>>

        However, when they are matched against each other or compared using the =:= │ │ │ operator, they are considered to be equal. Thus, 0.0 =:= -0.0 currently │ │ │ returns true.

        In Erlang/OTP 27, 0.0 =:= -0.0 will return false, and matching 0.0 against │ │ │ -0.0 will fail. When used as map keys, 0.0 and -0.0 will be considered to │ │ │ be distinct.

        The == operator will continue to return true for 0.0 == -0.0.

        To help to find code that might need to be revised, in OTP 27 there will be a │ │ │ new compiler warning when matching against 0.0 or comparing to that value │ │ │ using the =:= operator. The warning can be suppressed by matching against │ │ │ +0.0 instead of 0.0.

        We plan to introduce the same warning in OTP 26.1, but by default it will be │ │ │ disabled.

        │ │ │ │ │ │ │ │ │ │ │ │ Singleton type variables will become a compile-time error │ │ │

        │ │ │ -

        Before Erlang/OTP 26, the compiler would silenty accept the following spec:

        -spec f(Opts) -> term() when
        │ │ │ -    Opts :: {ok, Unknown} | {error, Unknown}.
        │ │ │ -f(_) -> error.

        In OTP 26, the compiler emits a warning pointing out that the type variable │ │ │ -Unknown is unbound:

        t.erl:6:18: Warning: type variable 'Unknown' is only used once (is unbound)
        │ │ │ +

        Before Erlang/OTP 26, the compiler would silenty accept the following spec:

        -spec f(Opts) -> term() when
        │ │ │ +    Opts :: {ok, Unknown} | {error, Unknown}.
        │ │ │ +f(_) -> error.

        In OTP 26, the compiler emits a warning pointing out that the type variable │ │ │ +Unknown is unbound:

        t.erl:6:18: Warning: type variable 'Unknown' is only used once (is unbound)
        │ │ │  %    6|     Opts :: {ok, Unknown} | {error, Unknown}.
        │ │ │  %     |                  ^

        In OTP 27, that warning will become an error.

        │ │ │ │ │ │ │ │ │ │ │ │ Escripts will be compiled by default │ │ │

        │ │ │

        Escripts will be compiled by default instead of interpreted. That means that the │ │ │ compiler application must be available.

        The old behavior of interpreting escripts can be restored by adding the │ │ │ -following line to the script file:

        -mode(interpret).

        In OTP 28, support for interpreting an escript will be removed.

        │ │ │ +following line to the script file:

        -mode(interpret).

        In OTP 28, support for interpreting an escript will be removed.

        │ │ │ │ │ │ │ │ │ │ │ │ -code_path_choice will default to strict │ │ │

        │ │ │

        This command line option controls if paths given in the command line, boot │ │ │ scripts, and the code server should be interpreted as is strict or relaxed.

        OTP 26 and earlier defaults to relaxed, which means -pa myapp/ebin would │ │ │ @@ -231,18 +231,18 @@ │ │ │ " │ │ │ String Content │ │ │ " │ │ │ %% │ │ │ %% In OTP 27 it is instead interpreted as a │ │ │ %% Triple-Quoted String equivalent to │ │ │ "String Content"

        """"
        │ │ │ -++ foo() ++
        │ │ │ +++ foo() ++
        │ │ │  """"
        │ │ │  %% Became
        │ │ │ -"" ++ foo() ++ ""
        │ │ │ +"" ++ foo() ++ ""
        │ │ │  %%
        │ │ │  %% In OTP 27 it is instead interpreted as a
        │ │ │  %% Triple-Quoted String (triple-or-more) equivalent to
        │ │ │  "++ foo() ++"

        From Erlang/OTP 26.1 up to 27.0 the compiler issues a warning for a sequence of │ │ │ 3 or more double-quote characters since that is almost certainly a mistake or │ │ │ something like a result of bad automatic code generation. If a users gets that │ │ │ warning, the code should be corrected for example by inserting appropriate │ │ ├── ./usr/share/doc/erlang-doc/html/erts-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,44 +350,44 @@ │ │ │ accept_connection/5.

      • other_node - Name of the other node. This field │ │ │ is only mandatory when this node initiates the connection. That is, when │ │ │ connection is set up via setup/5.

      • this_node - The node name of this node.

      • socket - The identifier of the distribution │ │ │ controller.

      • timer - The timer created using │ │ │ dist_util:start_timer/1.

      • allowed - Information passed as Allowed to │ │ │ accept_connection/5. This field is only mandatory when the remote node │ │ │ initiated the connection. That is, when the connection is set up via │ │ │ -accept_connection/5.

      • f_send - A fun with the following signature:

        fun (DistCtrlr, Data) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller and Data │ │ │ -is io data to pass to the other side.

        Only used during handshake phase.

      • f_recv - A fun with the following signature:

        fun (DistCtrlr, Length, Timeout) -> {ok, Packet} | {error, Reason}

        where DistCtrlr is the identifier of the distribution controller. If │ │ │ +accept_connection/5.

      • f_send - A fun with the following signature:

        fun (DistCtrlr, Data) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller and Data │ │ │ +is io data to pass to the other side.

        Only used during handshake phase.

      • f_recv - A fun with the following signature:

        fun (DistCtrlr, Length, Timeout) -> {ok, Packet} | {error, Reason}

        where DistCtrlr is the identifier of the distribution controller. If │ │ │ Length is 0, all available bytes should be returned. If Length > 0, │ │ │ exactly Length bytes should be returned, or an error; possibly discarding │ │ │ less than Length bytes of data when the connection is closed from the other │ │ │ side. It is used for passive receive of data from the other end.

        Only used during handshake phase.

      • f_setopts_pre_nodeup - A fun with the │ │ │ -following signature:

        fun (DistCtrlr) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller. Called │ │ │ +following signature:

        fun (DistCtrlr) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller. Called │ │ │ just before the distribution channel is taken up for normal traffic.

        Only used during handshake phase.

      • f_setopts_post_nodeup - A fun with │ │ │ -the following signature:

        fun (DistCtrlr) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller. Called │ │ │ -just after distribution channel has been taken up for normal traffic.

        Only used during handshake phase.

      • f_getll - A fun with the following signature:

        fun (DistCtrlr) -> ID

        where DistCtrlr is the identifier of the distribution controller and ID is │ │ │ +the following signature:

        fun (DistCtrlr) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller. Called │ │ │ +just after distribution channel has been taken up for normal traffic.

        Only used during handshake phase.

      • f_getll - A fun with the following signature:

        fun (DistCtrlr) -> ID

        where DistCtrlr is the identifier of the distribution controller and ID is │ │ │ the identifier of the low level entity that handles the connection (often │ │ │ -DistCtrlr itself).

        Only used during handshake phase.

      • f_address - A fun with the following signature:

        fun (DistCtrlr, Node) -> NetAddress

        where DistCtrlr is the identifier of the distribution controller, Node is │ │ │ +DistCtrlr itself).

        Only used during handshake phase.

      • f_address - A fun with the following signature:

        fun (DistCtrlr, Node) -> NetAddress

        where DistCtrlr is the identifier of the distribution controller, Node is │ │ │ the node name of the node on the other end, and NetAddress is a │ │ │ #net_address{} record with information about the address for the Node on │ │ │ the other end of the connection. The #net_address{} record is defined in │ │ │ -kernel/include/net_address.hrl.

        Only used during handshake phase.

      • mf_tick - A fun with the following signature:

        fun (DistCtrlr) -> void()

        where DistCtrlr is the identifier of the distribution controller. This │ │ │ +kernel/include/net_address.hrl.

        Only used during handshake phase.

      • mf_tick - A fun with the following signature:

        fun (DistCtrlr) -> void()

        where DistCtrlr is the identifier of the distribution controller. This │ │ │ function should send information over the connection that is not interpreted │ │ │ by the other end while increasing the statistics of received packets on the │ │ │ other end. This is usually implemented by sending an empty packet.

        Note

        It is of vital importance that this operation does not block the caller for │ │ │ -a long time. This since it is called from the connection supervisor.

        Used when connection is up.

      • mf_getstat - A fun with the following signature:

        fun (DistCtrlr) -> {ok, Received, Sent, PendSend}

        where DistCtrlr is the identifier of the distribution controller, Received │ │ │ +a long time. This since it is called from the connection supervisor.

        Used when connection is up.

      • mf_getstat - A fun with the following signature:

        fun (DistCtrlr) -> {ok, Received, Sent, PendSend}

        where DistCtrlr is the identifier of the distribution controller, Received │ │ │ is received packets, Sent is sent packets, and PendSend is amount of data │ │ │ in queue to be sent (typically in bytes, but dist_util only checks whether │ │ │ the value is non-zero to know there is data in queue) or a boolean/0 │ │ │ indicating whether there are packets in queue to be sent.

        Note

        It is of vital importance that this operation does not block the caller for │ │ │ a long time. This since it is called from the connection supervisor.

        Used when connection is up.

      • request_type - The request Type as passed to │ │ │ setup/5. This is only mandatory when the connection has │ │ │ -been initiated by this node. That is, the connection is set up via setup/5.

      • mf_setopts - A fun with the following signature:

        fun (DistCtrl, Opts) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller and Opts │ │ │ -is a list of options to set on the connection.

        This function is optional. Used when connection is up.

      • mf_getopts - A fun with the following signature:

        fun (DistCtrl, Opts) -> {ok, OptionValues} | {error, Error}

        where DistCtrlr is the identifier of the distribution controller and Opts │ │ │ +been initiated by this node. That is, the connection is set up via setup/5.

      • mf_setopts - A fun with the following signature:

        fun (DistCtrl, Opts) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller and Opts │ │ │ +is a list of options to set on the connection.

        This function is optional. Used when connection is up.

      • mf_getopts - A fun with the following signature:

        fun (DistCtrl, Opts) -> {ok, OptionValues} | {error, Error}

        where DistCtrlr is the identifier of the distribution controller and Opts │ │ │ is a list of options to read for the connection.

        This function is optional. Used when connection is up.

      • f_handshake_complete - A fun with the │ │ │ -following signature:

        fun (DistCtrlr, Node, DHandle) -> void()

        where DistCtrlr is the identifier of the distribution controller, Node is │ │ │ +following signature:

        fun (DistCtrlr, Node, DHandle) -> void()

        where DistCtrlr is the identifier of the distribution controller, Node is │ │ │ the node name of the node connected at the other end, and DHandle is a │ │ │ distribution handle needed by a distribution controller process when calling │ │ │ the following BIFs:

        This function is called when the handshake has completed and the distribution │ │ │ channel is up. The distribution controller can begin dispatching traffic over │ │ │ the channel. This function is optional.

        Only used during handshake phase.

      • add_flags - │ │ │ Distribution flags to add to the connection. │ │ │ Currently all (non obsolete) flags will automatically be enabled.

        This flag field is optional.

      • reject_flags - │ │ ├── ./usr/share/doc/erlang-doc/html/erts-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,15 +680,15 @@ │ │ │ match both source and destination registers. As an operand in a specific │ │ │ instruction, it must only be used for a destination register.)

  • o - Overflow. An untagged integer that does not fit in a machine word.

  • Predicates

    If the constraints described so far is not enough, additional │ │ │ constraints can be implemented in C and be called as a guard function │ │ │ on the left-hand side of the transformation. If the guard function returns │ │ │ a non-zero value, the matching of the rule will continue, otherwise │ │ │ the match will fail. Such guard functions are hereafter called │ │ │ predicates.

    The most commonly used guard constraints is equal(). It can be used │ │ │ -to remove a redundant move instructio like this:

    move R1 R2 | equal(R1, R2) => _

    or remove a redundant is_eq_exact instruction like this:

    is_eq_exact Lbl Src1 Src2 | equal(Src1, Src2) => _

    At the time of writing, all predicates are defined in files named │ │ │ +to remove a redundant move instructio like this:

    move R1 R2 | equal(R1, R2) => _

    or remove a redundant is_eq_exact instruction like this:

    is_eq_exact Lbl Src1 Src2 | equal(Src1, Src2) => _

    At the time of writing, all predicates are defined in files named │ │ │ predicates.tab in several directories. In predicates.tab directly │ │ │ in $ERL_TOP/erts/emulator/beam, predicates that are used by both the │ │ │ traditinal emulator and the JIT implementations are contained. │ │ │ Predicates only used by the emulator can be found in │ │ │ emu/predicates.tab.

    │ │ │ │ │ │ │ │ │ @@ -696,41 +696,41 @@ │ │ │ A very brief note on implementation of predicates │ │ │

    │ │ │

    It is outside the scope for this document to describe in detail how │ │ │ predicates are implemented because it requires knowledge of the │ │ │ internal loader data structures, but here is quick look at the │ │ │ implementation of a simple predicate called literal_is_map().

    Here is first an example how it is used:

    ismap Fail Lit=q | literal_is_map(Lit) =>

    If the Lit operand is a literal, then the literal_is_map() │ │ │ predicate is called to determine whether it is a map literal. │ │ │ -If it is, the instruction is not needed and can be removed.

    literal_is_map() is implemented like this (in emu/predicates.tab):

    pred.literal_is_map(Lit) {
    │ │ │ +If it is, the instruction is not needed and can be removed.

    literal_is_map() is implemented like this (in emu/predicates.tab):

    pred.literal_is_map(Lit) {
    │ │ │      Eterm term;
    │ │ │  
    │ │ │ -    ASSERT(Lit.type == TAG_q);
    │ │ │ -    term = beamfile_get_literal(&S->beam, Lit.val);
    │ │ │ -    return is_map(term);
    │ │ │ -}

    The pred. prefix tells beam_makeops that this function is a │ │ │ + ASSERT(Lit.type == TAG_q); │ │ │ + term = beamfile_get_literal(&S->beam, Lit.val); │ │ │ + return is_map(term); │ │ │ +}

    The pred. prefix tells beam_makeops that this function is a │ │ │ predicate. Without the prefix, it would have been interpreted as the │ │ │ implementation of an instruction (described in Defining the │ │ │ implementation).

    Predicate functions have a magic variabled called S, which is a │ │ │ pointer to a state struct. In the example, │ │ │ beamfile_get_literal(&S->beam, Lit.val); is used to retrieve the actual term │ │ │ for the literal.

    At the time of writing, the expanded C code generated by │ │ │ -beam_makeops looks like this:

    static int literal_is_map(LoaderState* S, BeamOpArg Lit) {
    │ │ │ +beam_makeops looks like this:

    static int literal_is_map(LoaderState* S, BeamOpArg Lit) {
    │ │ │    Eterm term;
    │ │ │  
    │ │ │ -  ASSERT(Lit.type == TAG_q);
    │ │ │ -  term = S->literals[Lit.val].term;
    │ │ │ -  return is_map(term);;
    │ │ │ -}

    Handling instructions with variable number of operands

    Some instructions, such as select_val/3, essentially has a variable │ │ │ + ASSERT(Lit.type == TAG_q); │ │ │ + term = S->literals[Lit.val].term; │ │ │ + return is_map(term);; │ │ │ +}

    Handling instructions with variable number of operands

    Some instructions, such as select_val/3, essentially has a variable │ │ │ number of operands. Such instructions have a {list,[...]} operand │ │ │ -as their last operand in the BEAM assembly code. For example:

    {select_val,{x,0},
    │ │ │ -            {f,1},
    │ │ │ -            {list,[{atom,b},{f,4},{atom,a},{f,5}]}}.

    The loader will convert a {list,[...]} operand to an u operand whose │ │ │ +as their last operand in the BEAM assembly code. For example:

    {select_val,{x,0},
    │ │ │ +            {f,1},
    │ │ │ +            {list,[{atom,b},{f,4},{atom,a},{f,5}]}}.

    The loader will convert a {list,[...]} operand to an u operand whose │ │ │ value is the number of elements in the list, followed by each element in │ │ │ the list. The instruction above would be translated to the following │ │ │ -generic instruction:

    {select_val,{x,0},{f,1},{u,4},{atom,b},{f,4},{atom,a},{f,5}}

    To match a variable number of arguments we need to use the special │ │ │ +generic instruction:

    {select_val,{x,0},{f,1},{u,4},{atom,b},{f,4},{atom,a},{f,5}}

    To match a variable number of arguments we need to use the special │ │ │ operand type * like this:

    select_val Src=aiq Fail=f Size=u List=* =>
    │ │ │      i_const_select_val Src Fail Size List

    This transformation renames a select_val/3 instruction │ │ │ with a constant source operand to i_const_select_val/3.

    Constructing new instructions on the right-hand side

    The most common operand on the right-hand side is a variable that was │ │ │ bound while matching the pattern on the left-hand side. For example:

    trim N Remaining => i_trim N

    An operand can also be a type letter to construct an operand of that │ │ │ type. Each type has a default value. For example, the type x has │ │ │ the default value 1023, which is the highest X register. That makes │ │ │ x on the right-hand side a convenient shortcut for a temporary X │ │ │ @@ -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 variabled called S, which is a │ │ │ pointer to a state struct. In the example, S is used in the invocation │ │ │ of the NewBeamOp macro.

    │ │ │ │ │ │ │ │ │ @@ -819,473 +819,473 @@ │ │ │ msg_instrs.tab │ │ │ select_instrs.tab │ │ │ trace_instrs.tab

    There is also a file that only contains macro definitions:

    macros.tab

    The syntax of each file is similar to C code. In fact, most of │ │ │ the contents is C code, interspersed with macro invocations.

    To allow Emacs to auto-indent the code, each file starts with the │ │ │ following line:

    // -*- c -*-

    To avoid messing up the indentation, all comments are written │ │ │ as C++ style comments (//) instead of #. Note that a comment │ │ │ must start at the beginning of a line.

    The meat of an instruction definition file are macro definitions. │ │ │ -We have seen this macro definition before:

    move(Src, Dst) {
    │ │ │ +We have seen this macro definition before:

    move(Src, Dst) {
    │ │ │      $Dst = $Src;
    │ │ │ -}

    A macro definitions must start at the beginning of the line (no spaces │ │ │ +}

    A macro definitions must start at the beginning of the line (no spaces │ │ │ allowed), the opening curly bracket must be on the same line, and the │ │ │ finishing curly bracket must be at the beginning of a line. It is │ │ │ recommended that the macro body is properly indented.

    As a convention, the macro arguments in the head all start with an │ │ │ uppercase letter. In the body, the macro arguments can be expanded │ │ │ by preceding them with $.

    A macro definition whose name and arity matches a family of │ │ │ specific instructions is assumed to be the implementation of that │ │ │ instruction.

    A macro can also be invoked from within another macro. For example, │ │ │ move_deallocate_return/2 avoids repeating code by invoking │ │ │ -$deallocate_return() as a macro:

    move_deallocate_return(Src, Deallocate) {
    │ │ │ -    x(0) = $Src;
    │ │ │ -    $deallocate_return($Deallocate);
    │ │ │ -}

    Here is the definition of deallocate_return/1:

    deallocate_return(Deallocate) {
    │ │ │ +$deallocate_return() as a macro:

    move_deallocate_return(Src, Deallocate) {
    │ │ │ +    x(0) = $Src;
    │ │ │ +    $deallocate_return($Deallocate);
    │ │ │ +}

    Here is the definition of deallocate_return/1:

    deallocate_return(Deallocate) {
    │ │ │      //| -no_next
    │ │ │      int words_to_pop = $Deallocate;
    │ │ │ -    SET_I((BeamInstr *) cp_val(*E));
    │ │ │ -    E = ADD_BYTE_OFFSET(E, words_to_pop);
    │ │ │ -    CHECK_TERM(x(0));
    │ │ │ +    SET_I((BeamInstr *) cp_val(*E));
    │ │ │ +    E = ADD_BYTE_OFFSET(E, words_to_pop);
    │ │ │ +    CHECK_TERM(x(0));
    │ │ │      DispatchReturn;
    │ │ │ -}

    The expanded code for move_deallocate_return will look this:

    OpCase(move_deallocate_return_cQ):
    │ │ │ -{
    │ │ │ -  x(0) = I[1];
    │ │ │ -  do {
    │ │ │ -    int words_to_pop = Qb(BeamExtraData(I[0]));
    │ │ │ -    SET_I((BeamInstr *) cp_val(*E));
    │ │ │ -    E = ADD_BYTE_OFFSET(E, words_to_pop);
    │ │ │ -    CHECK_TERM(x(0));
    │ │ │ +}

    The expanded code for move_deallocate_return will look this:

    OpCase(move_deallocate_return_cQ):
    │ │ │ +{
    │ │ │ +  x(0) = I[1];
    │ │ │ +  do {
    │ │ │ +    int words_to_pop = Qb(BeamExtraData(I[0]));
    │ │ │ +    SET_I((BeamInstr *) cp_val(*E));
    │ │ │ +    E = ADD_BYTE_OFFSET(E, words_to_pop);
    │ │ │ +    CHECK_TERM(x(0));
    │ │ │      DispatchReturn;
    │ │ │ -  } while (0);
    │ │ │ -}

    When expanding macros, beam_makeops wraps the expansion in a │ │ │ + } while (0); │ │ │ +}

    When expanding macros, beam_makeops wraps the expansion in a │ │ │ do/while wrapper unless beam_makeops can clearly see that no │ │ │ wrapper is needed. In this case, the wrapper is needed.

    Note that arguments for macros cannot be complex expressions, because │ │ │ the arguments are split on ,. For example, the following would │ │ │ not work because beam_makeops would split the expression into │ │ │ -two arguments:

    $deallocate_return(get_deallocation(y, $Deallocate));

    Code generation directives

    Within macro definitions, // comments are in general not treated │ │ │ +two arguments:

    $deallocate_return(get_deallocation(y, $Deallocate));

    Code generation directives

    Within macro definitions, // comments are in general not treated │ │ │ specially. They will be copied to the file with the generated code │ │ │ along with the rest of code in the body.

    However, there is an exception. Within a macro definition, a line that │ │ │ starts with whitespace followed by //| is treated specially. The │ │ │ rest of the line is assumed to contain directives to control code │ │ │ generation.

    Currently, two code generation directives are recognized:

    The -no_prefetch directive

    To see what -no_prefetch does, let's first look at the default code │ │ │ -generation. Here is the code generated for move_cx:

    OpCase(move_cx):
    │ │ │ -{
    │ │ │ -  BeamInstr next_pf = BeamCodeAddr(I[2]);
    │ │ │ -  xb(BeamExtraData(I[0])) = I[1];
    │ │ │ +generation.  Here is the code generated for move_cx:

    OpCase(move_cx):
    │ │ │ +{
    │ │ │ +  BeamInstr next_pf = BeamCodeAddr(I[2]);
    │ │ │ +  xb(BeamExtraData(I[0])) = I[1];
    │ │ │    I += 2;
    │ │ │ -  ASSERT(VALID_INSTR(next_pf));
    │ │ │ -  GotoPF(next_pf);
    │ │ │ -}

    Note that the very first thing done is to fetch the address to the │ │ │ + ASSERT(VALID_INSTR(next_pf)); │ │ │ + GotoPF(next_pf); │ │ │ +}

    Note that the very first thing done is to fetch the address to the │ │ │ next instruction. The reason is that it usually improves performance.

    Just as a demonstration, we can add a -no_prefetch directive to │ │ │ -the move/2 instruction:

    move(Src, Dst) {
    │ │ │ +the move/2 instruction:

    move(Src, Dst) {
    │ │ │      //| -no_prefetch
    │ │ │      $Dst = $Src;
    │ │ │ -}

    We can see that the prefetch is no longer done:

    OpCase(move_cx):
    │ │ │ -{
    │ │ │ -  xb(BeamExtraData(I[0])) = I[1];
    │ │ │ +}

    We can see that the prefetch is no longer done:

    OpCase(move_cx):
    │ │ │ +{
    │ │ │ +  xb(BeamExtraData(I[0])) = I[1];
    │ │ │    I += 2;
    │ │ │ -  ASSERT(VALID_INSTR(*I));
    │ │ │ -  Goto(*I);
    │ │ │ -}

    When would we want to turn off the prefetch in practice?

    In instructions that will not always execute the next instruction. │ │ │ -For example:

    is_atom(Fail, Src) {
    │ │ │ -    if (is_not_atom($Src)) {
    │ │ │ -        $FAIL($Fail);
    │ │ │ -    }
    │ │ │ -}
    │ │ │ +  ASSERT(VALID_INSTR(*I));
    │ │ │ +  Goto(*I);
    │ │ │ +}

    When would we want to turn off the prefetch in practice?

    In instructions that will not always execute the next instruction. │ │ │ +For example:

    is_atom(Fail, Src) {
    │ │ │ +    if (is_not_atom($Src)) {
    │ │ │ +        $FAIL($Fail);
    │ │ │ +    }
    │ │ │ +}
    │ │ │  
    │ │ │  // From macros.tab
    │ │ │ -FAIL(Fail) {
    │ │ │ +FAIL(Fail) {
    │ │ │      //| -no_prefetch
    │ │ │ -    $SET_I_REL($Fail);
    │ │ │ -    Goto(*I);
    │ │ │ -}

    is_atom/2 may either execute the next instruction (if the second │ │ │ -operand is an atom) or branch to the failure label.

    The generated code looks like this:

    OpCase(is_atom_fx):
    │ │ │ -{
    │ │ │ -  if (is_not_atom(xb(I[1]))) {
    │ │ │ -    ASSERT(VALID_INSTR(*(I + (fb(BeamExtraData(I[0]))) + 0)));
    │ │ │ -    I += fb(BeamExtraData(I[0])) + 0;;
    │ │ │ -    Goto(*I);;
    │ │ │ -  }
    │ │ │ +    $SET_I_REL($Fail);
    │ │ │ +    Goto(*I);
    │ │ │ +}

    is_atom/2 may either execute the next instruction (if the second │ │ │ +operand is an atom) or branch to the failure label.

    The generated code looks like this:

    OpCase(is_atom_fx):
    │ │ │ +{
    │ │ │ +  if (is_not_atom(xb(I[1]))) {
    │ │ │ +    ASSERT(VALID_INSTR(*(I + (fb(BeamExtraData(I[0]))) + 0)));
    │ │ │ +    I += fb(BeamExtraData(I[0])) + 0;;
    │ │ │ +    Goto(*I);;
    │ │ │ +  }
    │ │ │    I += 2;
    │ │ │ -  ASSERT(VALID_INSTR(*I));
    │ │ │ -  Goto(*I);
    │ │ │ -}
    The -no_next directive

    Next we will look at when the -no_next directive can be used. Here │ │ │ -is the jump/1 instruction:

    jump(Fail) {
    │ │ │ -    $JUMP($Fail);
    │ │ │ -}
    │ │ │ +  ASSERT(VALID_INSTR(*I));
    │ │ │ +  Goto(*I);
    │ │ │ +}
    The -no_next directive

    Next we will look at when the -no_next directive can be used. Here │ │ │ +is the jump/1 instruction:

    jump(Fail) {
    │ │ │ +    $JUMP($Fail);
    │ │ │ +}
    │ │ │  
    │ │ │  // From macros.tab
    │ │ │ -JUMP(Fail) {
    │ │ │ +JUMP(Fail) {
    │ │ │      //| -no_next
    │ │ │ -    $SET_I_REL($Fail);
    │ │ │ -    Goto(*I);
    │ │ │ -}

    The generated code looks like this:

    OpCase(jump_f):
    │ │ │ -{
    │ │ │ -  ASSERT(VALID_INSTR(*(I + (fb(BeamExtraData(I[0]))) + 0)));
    │ │ │ -  I += fb(BeamExtraData(I[0])) + 0;;
    │ │ │ -  Goto(*I);;
    │ │ │ -}

    If we remove the -no_next directive, the code would look like this:

    OpCase(jump_f):
    │ │ │ -{
    │ │ │ -  BeamInstr next_pf = BeamCodeAddr(I[1]);
    │ │ │ -  ASSERT(VALID_INSTR(*(I + (fb(BeamExtraData(I[0]))) + 0)));
    │ │ │ -  I += fb(BeamExtraData(I[0])) + 0;;
    │ │ │ -  Goto(*I);;
    │ │ │ +    $SET_I_REL($Fail);
    │ │ │ +    Goto(*I);
    │ │ │ +}

    The generated code looks like this:

    OpCase(jump_f):
    │ │ │ +{
    │ │ │ +  ASSERT(VALID_INSTR(*(I + (fb(BeamExtraData(I[0]))) + 0)));
    │ │ │ +  I += fb(BeamExtraData(I[0])) + 0;;
    │ │ │ +  Goto(*I);;
    │ │ │ +}

    If we remove the -no_next directive, the code would look like this:

    OpCase(jump_f):
    │ │ │ +{
    │ │ │ +  BeamInstr next_pf = BeamCodeAddr(I[1]);
    │ │ │ +  ASSERT(VALID_INSTR(*(I + (fb(BeamExtraData(I[0]))) + 0)));
    │ │ │ +  I += fb(BeamExtraData(I[0])) + 0;;
    │ │ │ +  Goto(*I);;
    │ │ │    I += 1;
    │ │ │ -  ASSERT(VALID_INSTR(next_pf));
    │ │ │ -  GotoPF(next_pf);
    │ │ │ -}

    In the end, the C compiler will probably optimize this code to the │ │ │ + ASSERT(VALID_INSTR(next_pf)); │ │ │ + GotoPF(next_pf); │ │ │ +}

    In the end, the C compiler will probably optimize this code to the │ │ │ same native code as the first version, but the first version is certainly │ │ │ much easier to read for human readers.

    Macros in the macros.tab file

    The file macros.tab contains many useful macros. When implementing │ │ │ new instructions it is good practice to look through macros.tab to │ │ │ see if any of existing macros can be used rather than re-inventing │ │ │ the wheel.

    We will describe a few of the most useful macros here.

    The GC_REGEXP definition

    The following line defines a regular expression that will recognize │ │ │ a call to a function that does a garbage collection:

     GC_REGEXP=erts_garbage_collect|erts_gc|GcBifFunction;

    The purpose is that beam_makeops can verify that an instruction │ │ │ that does a garbage collection and has an d operand uses the │ │ │ $REFRESH_GEN_DEST() macro.

    If you need to define a new function that does garbage collection, │ │ │ you should give it the prefix erts_gc_. If that is not possible │ │ │ you should update the regular expression so that it will match your │ │ │ -new function.

    FAIL(Fail)

    Branch to $Fail. Will suppress prefetch (-no_prefetch). Typical use:

    is_nonempty_list(Fail, Src) {
    │ │ │ -    if (is_not_list($Src)) {
    │ │ │ -        $FAIL($Fail);
    │ │ │ -    }
    │ │ │ -}
    JUMP(Fail)

    Branch to $Fail. Suppresses generation of dispatch of the next │ │ │ -instruction (-no_next). Typical use:

    jump(Fail) {
    │ │ │ -    $JUMP($Fail);
    │ │ │ -}
    GC_TEST(NeedStack, NeedHeap, Live)

    $GC_TEST(NeedStack, NeedHeap, Live) tests that given amount of │ │ │ +new function.

    FAIL(Fail)

    Branch to $Fail. Will suppress prefetch (-no_prefetch). Typical use:

    is_nonempty_list(Fail, Src) {
    │ │ │ +    if (is_not_list($Src)) {
    │ │ │ +        $FAIL($Fail);
    │ │ │ +    }
    │ │ │ +}
    JUMP(Fail)

    Branch to $Fail. Suppresses generation of dispatch of the next │ │ │ +instruction (-no_next). Typical use:

    jump(Fail) {
    │ │ │ +    $JUMP($Fail);
    │ │ │ +}
    GC_TEST(NeedStack, NeedHeap, Live)

    $GC_TEST(NeedStack, NeedHeap, Live) tests that given amount of │ │ │ stack space and heap space is available. If not it will do a │ │ │ -garbage collection. Typical use:

    test_heap(Nh, Live) {
    │ │ │ -    $GC_TEST(0, $Nh, $Live);
    │ │ │ -}
    AH(NeedStack, NeedHeap, Live)

    AH(NeedStack, NeedHeap, Live) allocates a stack frame and │ │ │ +garbage collection. Typical use:

    test_heap(Nh, Live) {
    │ │ │ +    $GC_TEST(0, $Nh, $Live);
    │ │ │ +}
    AH(NeedStack, NeedHeap, Live)

    AH(NeedStack, NeedHeap, Live) allocates a stack frame and │ │ │ optionally additional heap space.

    Pre-defined macros and variables

    beam_makeops defines several built-in macros and pre-bound variables.

    The NEXT_INSTRUCTION pre-bound variable

    The NEXT_INSTRUCTION is a pre-bound variable that is available in │ │ │ -all instructions. It expands to the address of the next instruction.

    Here is an example:

    i_call(CallDest) {
    │ │ │ +all instructions.  It expands to the address of the next instruction.

    Here is an example:

    i_call(CallDest) {
    │ │ │      //| -no_next
    │ │ │ -    $SAVE_CONTINUATION_POINTER($NEXT_INSTRUCTION);
    │ │ │ -    $DISPATCH_REL($CallDest);
    │ │ │ -}

    When calling a function, the return address is first stored in E[0] │ │ │ + $SAVE_CONTINUATION_POINTER($NEXT_INSTRUCTION); │ │ │ + $DISPATCH_REL($CallDest); │ │ │ +}

    When calling a function, the return address is first stored in E[0] │ │ │ (using the $SAVE_CONTINUATION_POINTER() macro), and then control is │ │ │ -transferred to the callee. Here is the generated code:

    OpCase(i_call_f):
    │ │ │ -{
    │ │ │ -    ASSERT(VALID_INSTR(*(I+2)));
    │ │ │ -    *E = (BeamInstr) (I+2);;
    │ │ │ +transferred to the callee.  Here is the generated code:

    OpCase(i_call_f):
    │ │ │ +{
    │ │ │ +    ASSERT(VALID_INSTR(*(I+2)));
    │ │ │ +    *E = (BeamInstr) (I+2);;
    │ │ │  
    │ │ │      /* ... dispatch code intentionally left out ... */
    │ │ │ -}

    We can see that that $NEXT_INSTRUCTION has been expanded to I+2. │ │ │ +}

    We can see that that $NEXT_INSTRUCTION has been expanded to I+2. │ │ │ That makes sense since the size of the i_call_f/1 instruction is │ │ │ two words.

    The IP_ADJUSTMENT pre-bound variable

    $IP_ADJUSTMENT is usually 0. In a few combined instructions │ │ │ (described below) it can be non-zero. It is used like this │ │ │ -in macros.tab:

    SET_I_REL(Offset) {
    │ │ │ -    ASSERT(VALID_INSTR(*(I + ($Offset) + $IP_ADJUSTMENT)));
    │ │ │ +in macros.tab:

    SET_I_REL(Offset) {
    │ │ │ +    ASSERT(VALID_INSTR(*(I + ($Offset) + $IP_ADJUSTMENT)));
    │ │ │      I += $Offset + $IP_ADJUSTMENT;
    │ │ │ -}

    Avoid using IP_ADJUSTMENT directly. Use SET_I_REL() or │ │ │ +}

    Avoid using IP_ADJUSTMENT directly. Use SET_I_REL() or │ │ │ one of the macros that invoke such as FAIL() or JUMP() │ │ │ defined in macros.tab.

    Pre-defined macro functions

    The IF() macro

    $IF(Expr, IfTrue, IfFalse) evaluates Expr, which must be a valid │ │ │ Perl expression (which for simple numeric expressions have the same │ │ │ syntax as C). If Expr evaluates to 0, the entire IF() expression will be │ │ │ replaced with IfFalse, otherwise it will be replaced with IfTrue.

    See the description of OPERAND_POSITION() for an example.

    The OPERAND_POSITION() macro

    $OPERAND_POSITION(Expr) returns the position for Expr, if │ │ │ Expr is an operand that is not packed. The first operand is │ │ │ -at position 1.

    Returns 0 otherwise.

    This macro could be used like this in order to share code:

    FAIL(Fail) {
    │ │ │ +at position 1.

    Returns 0 otherwise.

    This macro could be used like this in order to share code:

    FAIL(Fail) {
    │ │ │      //| -no_prefetch
    │ │ │ -    $IF($OPERAND_POSITION($Fail) == 1 && $IP_ADJUSTMENT == 0,
    │ │ │ +    $IF($OPERAND_POSITION($Fail) == 1 && $IP_ADJUSTMENT == 0,
    │ │ │          goto common_jump,
    │ │ │ -        $DO_JUMP($Fail));
    │ │ │ -}
    │ │ │ +        $DO_JUMP($Fail));
    │ │ │ +}
    │ │ │  
    │ │ │ -DO_JUMP(Fail) {
    │ │ │ -    $SET_I_REL($Fail);
    │ │ │ -    Goto(*I));
    │ │ │ -}
    │ │ │ +DO_JUMP(Fail) {
    │ │ │ +    $SET_I_REL($Fail);
    │ │ │ +    Goto(*I));
    │ │ │ +}
    │ │ │  
    │ │ │  // In beam_emu.c:
    │ │ │  common_jump:
    │ │ │ -   I += I[1];
    │ │ │ -   Goto(*I));

    The $REFRESH_GEN_DEST() macro

    When a specific instruction has a d operand, early during execution │ │ │ + I += I[1]; │ │ │ + Goto(*I));

    The $REFRESH_GEN_DEST() macro

    When a specific instruction has a d operand, early during execution │ │ │ of the instruction, a pointer will be initialized to point to the X or │ │ │ Y register in question.

    If there is a garbage collection before the result is stored, │ │ │ the stack will move and if the d operand referred to a Y │ │ │ register, the pointer will no longer be valid. (Y registers are │ │ │ stored on the stack.)

    In those circumstances, $REFRESH_GEN_DEST() must be invoked │ │ │ to set up the pointer again. beam_makeops will notice │ │ │ if there is a call to a function that does a garbage collection and │ │ │ $REFRESH_GEN_DEST() is not called.

    Here is a complete example. The new_map instruction is defined │ │ │ -like this:

    new_map d t I

    It is implemented like this:

    new_map(Dst, Live, N) {
    │ │ │ +like this:

    new_map d t I

    It is implemented like this:

    new_map(Dst, Live, N) {
    │ │ │      Eterm res;
    │ │ │  
    │ │ │      HEAVY_SWAPOUT;
    │ │ │ -    res = erts_gc_new_map(c_p, reg, $Live, $N, $NEXT_INSTRUCTION);
    │ │ │ +    res = erts_gc_new_map(c_p, reg, $Live, $N, $NEXT_INSTRUCTION);
    │ │ │      HEAVY_SWAPIN;
    │ │ │ -    $REFRESH_GEN_DEST();
    │ │ │ +    $REFRESH_GEN_DEST();
    │ │ │      $Dst = res;
    │ │ │ -    $NEXT($NEXT_INSTRUCTION+$N);
    │ │ │ -}

    If we have forgotten the $REFRESH_GEN_DEST() there would be a message │ │ │ -similar to this:

    pointer to destination register is invalid after GC -- use $REFRESH_GEN_DEST()
    │ │ │ -... from the body of new_map at beam/map_instrs.tab(30)

    Variable number of operands

    Here follows an example of how to handle an instruction with a variable number │ │ │ + $NEXT($NEXT_INSTRUCTION+$N); │ │ │ +}

    If we have forgotten the $REFRESH_GEN_DEST() there would be a message │ │ │ +similar to this:

    pointer to destination register is invalid after GC -- use $REFRESH_GEN_DEST()
    │ │ │ +... from the body of new_map at beam/map_instrs.tab(30)

    Variable number of operands

    Here follows an example of how to handle an instruction with a variable number │ │ │ of operands for the interpreter. Here is the instruction definition in emu/ops.tab:

    put_tuple2 xy I *

    For the interpreter, the * is optional, because it does not effect code generation │ │ │ in any way. However, it is recommended to include it to make it clear for human readers │ │ │ that there is a variable number of operands.

    Use the $NEXT_INSTRUCTION macro to obtain a pointer to the first of the variable │ │ │ -operands.

    Here is the implementation:

    put_tuple2(Dst, Arity) {
    │ │ │ +operands.

    Here is the implementation:

    put_tuple2(Dst, Arity) {
    │ │ │  Eterm* hp = HTOP;
    │ │ │  Eterm arity = $Arity;
    │ │ │ -Eterm* dst_ptr = &($Dst);
    │ │ │ +Eterm* dst_ptr = &($Dst);
    │ │ │  
    │ │ │  //| -no_next
    │ │ │ -ASSERT(arity != 0);
    │ │ │ -*hp++ = make_arityval(arity);
    │ │ │ +ASSERT(arity != 0);
    │ │ │ +*hp++ = make_arityval(arity);
    │ │ │  
    │ │ │  /*
    │ │ │   * The $NEXT_INSTRUCTION macro points just beyond the fixed
    │ │ │   * operands. In this case it points to the descriptor of
    │ │ │   * the first element to be put into the tuple.
    │ │ │   */
    │ │ │  I = $NEXT_INSTRUCTION;
    │ │ │ -do {
    │ │ │ +do {
    │ │ │      Eterm term = *I++;
    │ │ │ -    switch (loader_tag(term)) {
    │ │ │ +    switch (loader_tag(term)) {
    │ │ │      case LOADER_X_REG:
    │ │ │ -    *hp++ = x(loader_x_reg_index(term));
    │ │ │ +    *hp++ = x(loader_x_reg_index(term));
    │ │ │      break;
    │ │ │      case LOADER_Y_REG:
    │ │ │ -    *hp++ = y(loader_y_reg_index(term));
    │ │ │ +    *hp++ = y(loader_y_reg_index(term));
    │ │ │      break;
    │ │ │      default:
    │ │ │      *hp++ = term;
    │ │ │      break;
    │ │ │ -    }
    │ │ │ -} while (--arity != 0);
    │ │ │ -*dst_ptr = make_tuple(HTOP);
    │ │ │ +    }
    │ │ │ +} while (--arity != 0);
    │ │ │ +*dst_ptr = make_tuple(HTOP);
    │ │ │  HTOP = hp;
    │ │ │ -ASSERT(VALID_INSTR(* (Eterm *)I));
    │ │ │ -Goto(*I);
    │ │ │ -}

    Combined instructions

    Problem: For frequently executed instructions we want to use │ │ │ +ASSERT(VALID_INSTR(* (Eterm *)I)); │ │ │ +Goto(*I); │ │ │ +}

    Combined instructions

    Problem: For frequently executed instructions we want to use │ │ │ "fast" operands types such as x and y, as opposed to s or S. │ │ │ To avoid an explosion in code size, we want to share most of the │ │ │ implementation between the instructions. Here are the specific │ │ │ instructions for i_increment/5:

    i_increment r W t d
    │ │ │  i_increment x W t d
    │ │ │ -i_increment y W t d

    The i_increment instruction is implemented like this:

    i_increment(Source, IncrementVal, Live, Dst) {
    │ │ │ +i_increment y W t d

    The i_increment instruction is implemented like this:

    i_increment(Source, IncrementVal, Live, Dst) {
    │ │ │      Eterm increment_reg_source = $Source;
    │ │ │      Eterm increment_val = $IncrementVal;
    │ │ │      Uint live;
    │ │ │      Eterm result;
    │ │ │  
    │ │ │ -    if (ERTS_LIKELY(is_small(increment_reg_val))) {
    │ │ │ -        Sint i = signed_val(increment_reg_val) + increment_val;
    │ │ │ -        if (ERTS_LIKELY(IS_SSMALL(i))) {
    │ │ │ -            $Dst = make_small(i);
    │ │ │ -            $NEXT0();
    │ │ │ -        }
    │ │ │ -    }
    │ │ │ +    if (ERTS_LIKELY(is_small(increment_reg_val))) {
    │ │ │ +        Sint i = signed_val(increment_reg_val) + increment_val;
    │ │ │ +        if (ERTS_LIKELY(IS_SSMALL(i))) {
    │ │ │ +            $Dst = make_small(i);
    │ │ │ +            $NEXT0();
    │ │ │ +        }
    │ │ │ +    }
    │ │ │      live = $Live;
    │ │ │      HEAVY_SWAPOUT;
    │ │ │ -    reg[live] = increment_reg_val;
    │ │ │ -    reg[live+1] = make_small(increment_val);
    │ │ │ -    result = erts_gc_mixed_plus(c_p, reg, live);
    │ │ │ +    reg[live] = increment_reg_val;
    │ │ │ +    reg[live+1] = make_small(increment_val);
    │ │ │ +    result = erts_gc_mixed_plus(c_p, reg, live);
    │ │ │      HEAVY_SWAPIN;
    │ │ │ -    ERTS_HOLE_CHECK(c_p);
    │ │ │ -    if (ERTS_LIKELY(is_value(result))) {
    │ │ │ -        $REFRESH_GEN_DEST();
    │ │ │ +    ERTS_HOLE_CHECK(c_p);
    │ │ │ +    if (ERTS_LIKELY(is_value(result))) {
    │ │ │ +        $REFRESH_GEN_DEST();
    │ │ │          $Dst = result;
    │ │ │ -        $NEXT0();
    │ │ │ -    }
    │ │ │ -    ASSERT(c_p->freason != BADMATCH || is_value(c_p->fvalue));
    │ │ │ +        $NEXT0();
    │ │ │ +    }
    │ │ │ +    ASSERT(c_p->freason != BADMATCH || is_value(c_p->fvalue));
    │ │ │      goto find_func_info;
    │ │ │ -}

    There will be three almost identical copies of the code. Given the │ │ │ +}

    There will be three almost identical copies of the code. Given the │ │ │ size of the code, that could be too high cost to pay.

    To avoid the three copies of the code, we could use only one specific │ │ │ instruction:

    i_increment S W t d

    (The same implementation as above will work.)

    That reduces the code size, but is slower because S means that │ │ │ there will be extra code to test whether the operand refers to an X │ │ │ register or a Y register.

    Solution: We can use "combined instructions". Combined │ │ │ instructions are combined from instruction fragments. The │ │ │ bulk of the code can be shared.

    Here we will show how i_increment can be implemented as a combined │ │ │ instruction. We will show each individual fragment first, and then │ │ │ show how to connect them together. First we will need a variable that │ │ │ -we can store the value fetched from the register in:

    increment.head() {
    │ │ │ +we can store the value fetched from the register in:

    increment.head() {
    │ │ │      Eterm increment_reg_val;
    │ │ │ -}

    The name increment is the name of the group that the fragment │ │ │ +}

    The name increment is the name of the group that the fragment │ │ │ belongs to. Note that it does not need to have the same │ │ │ name as the instruction. The group name is followed by . and │ │ │ the name of the fragment. The name head is pre-defined. │ │ │ The code in it will be placed at the beginning of a block, so │ │ │ that all fragments in the group can access it.

    Next we define the fragment that will pick up the value from the │ │ │ -register from the first operand:

    increment.fetch(Src) {
    │ │ │ +register from the first operand:

    increment.fetch(Src) {
    │ │ │      increment_reg_val = $Src;
    │ │ │ -}

    We call this fragment fetch. This fragment will be duplicated three │ │ │ -times, one for each value of the first operand (r, x, and y).

    Next we define the main part of the code that do the actual incrementing.

    increment.execute(IncrementVal, Live, Dst) {
    │ │ │ +}

    We call this fragment fetch. This fragment will be duplicated three │ │ │ +times, one for each value of the first operand (r, x, and y).

    Next we define the main part of the code that do the actual incrementing.

    increment.execute(IncrementVal, Live, Dst) {
    │ │ │      Eterm increment_val = $IncrementVal;
    │ │ │      Uint live;
    │ │ │      Eterm result;
    │ │ │  
    │ │ │ -    if (ERTS_LIKELY(is_small(increment_reg_val))) {
    │ │ │ -        Sint i = signed_val(increment_reg_val) + increment_val;
    │ │ │ -        if (ERTS_LIKELY(IS_SSMALL(i))) {
    │ │ │ -            $Dst = make_small(i);
    │ │ │ -            $NEXT0();
    │ │ │ -        }
    │ │ │ -    }
    │ │ │ +    if (ERTS_LIKELY(is_small(increment_reg_val))) {
    │ │ │ +        Sint i = signed_val(increment_reg_val) + increment_val;
    │ │ │ +        if (ERTS_LIKELY(IS_SSMALL(i))) {
    │ │ │ +            $Dst = make_small(i);
    │ │ │ +            $NEXT0();
    │ │ │ +        }
    │ │ │ +    }
    │ │ │      live = $Live;
    │ │ │      HEAVY_SWAPOUT;
    │ │ │ -    reg[live] = increment_reg_val;
    │ │ │ -    reg[live+1] = make_small(increment_val);
    │ │ │ -    result = erts_gc_mixed_plus(c_p, reg, live);
    │ │ │ +    reg[live] = increment_reg_val;
    │ │ │ +    reg[live+1] = make_small(increment_val);
    │ │ │ +    result = erts_gc_mixed_plus(c_p, reg, live);
    │ │ │      HEAVY_SWAPIN;
    │ │ │ -    ERTS_HOLE_CHECK(c_p);
    │ │ │ -    if (ERTS_LIKELY(is_value(result))) {
    │ │ │ -        $REFRESH_GEN_DEST();
    │ │ │ +    ERTS_HOLE_CHECK(c_p);
    │ │ │ +    if (ERTS_LIKELY(is_value(result))) {
    │ │ │ +        $REFRESH_GEN_DEST();
    │ │ │          $Dst = result;
    │ │ │ -        $NEXT0();
    │ │ │ -    }
    │ │ │ -    ASSERT(c_p->freason != BADMATCH || is_value(c_p->fvalue));
    │ │ │ +        $NEXT0();
    │ │ │ +    }
    │ │ │ +    ASSERT(c_p->freason != BADMATCH || is_value(c_p->fvalue));
    │ │ │      goto find_func_info;
    │ │ │ -}

    We call this fragment execute. It will handle the three remaining │ │ │ +}

    We call this fragment execute. It will handle the three remaining │ │ │ operands (W t d). There will only be one copy of this fragment.

    Now that we have defined the fragments, we need to inform │ │ │ beam_makeops how they should be connected:

    i_increment := increment.fetch.execute;

    To the left of the := is the name of the specific instruction that │ │ │ should be implemented by the fragments, in this case i_increment. │ │ │ To the right of := is the name of the group with the fragments, │ │ │ followed by a .. Then the name of the fragments in the group are │ │ │ listed in the order they should be executed. Note that the head │ │ │ fragment is not listed.

    The line ends in ; (to avoid messing up the indentation in Emacs).

    (Note that in practice the := line is usually placed before the │ │ │ -fragments.)

    The generated code looks like this:

    {
    │ │ │ +fragments.)

    The generated code looks like this:

    {
    │ │ │    Eterm increment_reg_val;
    │ │ │ -  OpCase(i_increment_rWtd):
    │ │ │ -  {
    │ │ │ -    increment_reg_val = r(0);
    │ │ │ -  }
    │ │ │ +  OpCase(i_increment_rWtd):
    │ │ │ +  {
    │ │ │ +    increment_reg_val = r(0);
    │ │ │ +  }
    │ │ │    goto increment__execute;
    │ │ │  
    │ │ │ -  OpCase(i_increment_xWtd):
    │ │ │ -  {
    │ │ │ -    increment_reg_val = xb(BeamExtraData(I[0]));
    │ │ │ -  }
    │ │ │ +  OpCase(i_increment_xWtd):
    │ │ │ +  {
    │ │ │ +    increment_reg_val = xb(BeamExtraData(I[0]));
    │ │ │ +  }
    │ │ │    goto increment__execute;
    │ │ │  
    │ │ │ -  OpCase(i_increment_yWtd):
    │ │ │ -  {
    │ │ │ -    increment_reg_val = yb(BeamExtraData(I[0]));
    │ │ │ -  }
    │ │ │ +  OpCase(i_increment_yWtd):
    │ │ │ +  {
    │ │ │ +    increment_reg_val = yb(BeamExtraData(I[0]));
    │ │ │ +  }
    │ │ │    goto increment__execute;
    │ │ │  
    │ │ │    increment__execute:
    │ │ │ -  {
    │ │ │ -    // Here follows the code from increment.execute()
    │ │ │ +  {
    │ │ │ +    // Here follows the code from increment.execute()
    │ │ │      .
    │ │ │      .
    │ │ │      .
    │ │ │ -}
    Some notes about combined instructions

    The operands that are different must be at │ │ │ +}

    Some notes about combined instructions

    The operands that are different must be at │ │ │ the beginning of the instruction. All operands in the last │ │ │ fragment must have the same operands in all variants of │ │ │ the specific instruction.

    As an example, the following specific instructions cannot be │ │ │ implemented as a combined instruction:

    i_times j? t x x d
    │ │ │  i_times j? t x y d
    │ │ │  i_times j? t s s d

    We would have to change the order of the operands so that the │ │ │ two operands that are different are placed first:

    i_times x x j? t d
    │ │ │  i_times x y j? t d
    │ │ │  i_times s s j? t d

    We can then define:

    i_times := times.fetch.execute;
    │ │ │  
    │ │ │ -times.head {
    │ │ │ +times.head {
    │ │ │      Eterm op1, op2;
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -times.fetch(Src1, Src2) {
    │ │ │ +times.fetch(Src1, Src2) {
    │ │ │      op1 = $Src1;
    │ │ │      op2 = $Src2;
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -times.execute(Fail, Live, Dst) {
    │ │ │ +times.execute(Fail, Live, Dst) {
    │ │ │      // Multiply op1 and op2.
    │ │ │      .
    │ │ │      .
    │ │ │      .
    │ │ │ -}

    Several instructions can share a group. As an example, the following │ │ │ +}

    Several instructions can share a group. As an example, the following │ │ │ instructions have different names, but in the end they all create a │ │ │ binary. The last two operands are common for all of them:

    i_bs_init_fail       xy j? t? x
    │ │ │  i_bs_init_fail_heap s I j? t? x
    │ │ │  i_bs_init                W t? x
    │ │ │  i_bs_init_heap         W I t? x

    The instructions are defined like this (formatted with extra │ │ │ spaces for clarity):

    i_bs_init_fail_heap := bs_init . fail_heap . verify . execute;
    │ │ │  i_bs_init_fail      := bs_init . fail      . verify . execute;
    │ │ │  i_bs_init           := bs_init .           .  plain . execute;
    │ │ │  i_bs_init_heap      := bs_init .               heap . execute;

    Note that the first two instruction have three fragments, while the │ │ │ -other two only have two fragments. Here are the fragments:

    bs_init_bits.head() {
    │ │ │ +other two only have two fragments.  Here are the fragments:

    bs_init_bits.head() {
    │ │ │      Eterm num_bits_term;
    │ │ │      Uint num_bits;
    │ │ │      Uint alloc;
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -bs_init_bits.plain(NumBits) {
    │ │ │ +bs_init_bits.plain(NumBits) {
    │ │ │      num_bits = $NumBits;
    │ │ │      alloc = 0;
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -bs_init_bits.heap(NumBits, Alloc) {
    │ │ │ +bs_init_bits.heap(NumBits, Alloc) {
    │ │ │      num_bits = $NumBits;
    │ │ │      alloc = $Alloc;
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -bs_init_bits.fail(NumBitsTerm) {
    │ │ │ +bs_init_bits.fail(NumBitsTerm) {
    │ │ │      num_bits_term = $NumBitsTerm;
    │ │ │      alloc = 0;
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -bs_init_bits.fail_heap(NumBitsTerm, Alloc) {
    │ │ │ +bs_init_bits.fail_heap(NumBitsTerm, Alloc) {
    │ │ │      num_bits_term = $NumBitsTerm;
    │ │ │      alloc = $Alloc;
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -bs_init_bits.verify(Fail) {
    │ │ │ +bs_init_bits.verify(Fail) {
    │ │ │      // Verify the num_bits_term, fail using $FAIL
    │ │ │      // if there is a problem.
    │ │ │  .
    │ │ │  .
    │ │ │  .
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -bs_init_bits.execute(Live, Dst) {
    │ │ │ +bs_init_bits.execute(Live, Dst) {
    │ │ │     // Long complicated code to a create a binary.
    │ │ │     .
    │ │ │     .
    │ │ │     .
    │ │ │ -}

    The full definitions of those instructions can be found in bs_instrs.tab. │ │ │ +}

    The full definitions of those instructions can be found in bs_instrs.tab. │ │ │ The generated code can be found in beam_warm.h.

    │ │ │ │ │ │ │ │ │ │ │ │ Code generation for BeamAsm │ │ │

    │ │ │

    For the BeamAsm runtime system, the implementation of each instruction is defined │ │ │ by emitter functions written in C++ that emit the assembly code for each instruction. │ │ │ There is one emitter function for each family of specific instructions.

    Take for example the move instruction. In beam/asm/ops.tab there is a │ │ │ -single specific instruction for move defined like this:

    move s d

    The implementation is found in beam/asm/instr_common.cpp:

    void BeamModuleAssembler::emit_move(const ArgVal &Src, const ArgVal &Dst) {
    │ │ │ -    mov_arg(Dst, Src);
    │ │ │ -}

    The mov_arg() helper function will handle all combinations of source and destination │ │ │ -operands. For example, the instruction {move,{x,1},{y,1}} will be translated like this:

    mov rdi, qword [rbx+8]
    │ │ │ -mov qword [rsp+8], rdi

    while {move,{integer,42},{x,0}} will be translated like this:

    mov qword [rbx], 687

    It is possible to define more than one specific instruction, but there will still be │ │ │ +single specific instruction for move defined like this:

    move s d

    The implementation is found in beam/asm/instr_common.cpp:

    void BeamModuleAssembler::emit_move(const ArgVal &Src, const ArgVal &Dst) {
    │ │ │ +    mov_arg(Dst, Src);
    │ │ │ +}

    The mov_arg() helper function will handle all combinations of source and destination │ │ │ +operands. For example, the instruction {move,{x,1},{y,1}} will be translated like this:

    mov rdi, qword [rbx+8]
    │ │ │ +mov qword [rsp+8], rdi

    while {move,{integer,42},{x,0}} will be translated like this:

    mov qword [rbx], 687

    It is possible to define more than one specific instruction, but there will still be │ │ │ only one emitter function. For example:

    fload S l
    │ │ │  fload q l

    By defining fload like this, the source operand must be a X register, Y register, or │ │ │ a literal. If not, the loading will be aborted. If the instruction instead had been │ │ │ defined like this:

    fload s l

    attempting to load an invalid instruction such as {fload,{atom,clearly_bad},{fr,0}} │ │ │ would cause a crash (either at load time or when the instruction was executed).

    Regardless on how many specific instructions there are in the family, │ │ │ -only a single emit_fload() function is allowed:

    void BeamModuleAssembler::emit_fload(const ArgVal &Src, const ArgVal &Dst) {
    │ │ │ +only a single emit_fload() function is allowed:

    void BeamModuleAssembler::emit_fload(const ArgVal &Src, const ArgVal &Dst) {
    │ │ │      .
    │ │ │      .
    │ │ │      .
    │ │ │ -}

    Handling a variable number of operands

    Here follows an example of how an instruction with a variable number │ │ │ +}

    Handling a variable number of operands

    Here follows an example of how an instruction with a variable number │ │ │ of operands could be handled. One such instructions is │ │ │ -select_val/3. Here is an example how it can look like in BEAM code:

    {select_val,{x,0},
    │ │ │ -            {f,1},
    │ │ │ -            {list,[{atom,b},{f,4},{atom,a},{f,5}]}}.

    The loader will convert a {list,[...]} operand to an u operand whose │ │ │ +select_val/3. Here is an example how it can look like in BEAM code:

    {select_val,{x,0},
    │ │ │ +            {f,1},
    │ │ │ +            {list,[{atom,b},{f,4},{atom,a},{f,5}]}}.

    The loader will convert a {list,[...]} operand to an u operand whose │ │ │ value is the number of elements in the list, followed by each element in │ │ │ the list. The instruction above would be translated to the following │ │ │ -instruction:

    {select_val,{x,0},{f,1},{u,4},{atom,b},{f,4},{atom,a},{f,5}}

    A definition of a specific instruction for that instruction would look │ │ │ +instruction:

    {select_val,{x,0},{f,1},{u,4},{atom,b},{f,4},{atom,a},{f,5}}

    A definition of a specific instruction for that instruction would look │ │ │ like this:

    select_val s f I *

    The * as the last operand will make sure that the variable operands │ │ │ are passed in as a Span of ArgVal (will be std::span in C++20 onwards). │ │ │ -Here is the emitter function:

    void BeamModuleAssembler::emit_select_val(const ArgVal &Src,
    │ │ │ +Here is the emitter function:

    void BeamModuleAssembler::emit_select_val(const ArgVal &Src,
    │ │ │                                            const ArgVal &Fail,
    │ │ │                                            const ArgVal &Size,
    │ │ │ -                                          const Span<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.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -7004,15 +7004,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
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -7152,35 +7152,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.

    │ │ │
    │ │ │ │ │ │ @@ -7212,16 +7212,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}
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -7275,17 +7275,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.

    │ │ │
    │ │ │ @@ -7353,19 +7353,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
    │ │ │ │ │ │ │ │ │
    │ │ │ @@ -7397,20 +7397,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]
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -7478,21 +7478,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
    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -7694,19 +7694,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
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -7736,19 +7736,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
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -7777,17 +7777,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.

    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ @@ -7815,16 +7815,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]
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -7854,15 +7854,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.

    │ │ │ │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -8006,17 +8006,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
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -8044,18 +8044,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>>]
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -8114,17 +8114,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
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -8244,19 +8244,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
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -8445,15 +8445,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}
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -8664,18 +8664,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}
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -8702,16 +8702,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}
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -8813,17 +8813,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:

      │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

      │ │ │ -
      1> float_to_list(7.12, [{decimals, 4}]).
      │ │ │ +
      1> float_to_list(7.12, [{decimals, 4}]).
      │ │ │  "7.1200"
      │ │ │ -2> float_to_list(7.12, [{decimals, 4}, compact]).
      │ │ │ +2> float_to_list(7.12, [{decimals, 4}, compact]).
      │ │ │  "7.12"
      │ │ │ -3> float_to_list(7.12, [{scientific, 3}]).
      │ │ │ +3> float_to_list(7.12, [{scientific, 3}]).
      │ │ │  "7.120e+00"
      │ │ │ -4> float_to_list(7.12, [short]).
      │ │ │ +4> float_to_list(7.12, [short]).
      │ │ │  "7.12"
      │ │ │ -5> float_to_list(0.1+0.2, [short]).
      │ │ │ +5> float_to_list(0.1+0.2, [short]).
      │ │ │  "0.30000000000000004"
      │ │ │ -6> float_to_list(0.1+0.2)
      │ │ │ +6> float_to_list(0.1+0.2)
      │ │ │  "3.00000000000000044409e-01"

      In the last example, float_to_list(0.1+0.2) evaluates to │ │ │ "3.00000000000000044409e-01". The reason for this is explained in │ │ │ Representation of Floating Point Numbers.

      │ │ │ │ │ │ │ │ │
      │ │ │ │ │ │ @@ -9850,19 +9850,19 @@ │ │ │ │ │ │

      Returns the largest integer not greater than Number.

      See also trunc/1.

      │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

      │ │ │ -
      1> floor(-10.5).
      │ │ │ +
      1> floor(-10.5).
      │ │ │  -11
      │ │ │ -2> floor(5.5).
      │ │ │ +2> floor(5.5).
      │ │ │  5
      │ │ │ -3> floor(10.0).
      │ │ │ +3> floor(10.0).
      │ │ │  10
      │ │ │
      │ │ │ │ │ │
      │ │ │ │ │ │
      │ │ │ │ │ │ @@ -9947,18 +9947,18 @@ │ │ │ new_uniq, uniq, and pid. For an external fun, the value of any of these │ │ │ items is always the atom undefined.

      See erlang:fun_info/1 for a description of the items.

      │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

      │ │ │ -
      1> erlang:fun_info(fun() -> ok end, type).
      │ │ │ -{type,local}
      │ │ │ -2> erlang:fun_info(fun lists:sum/1, type).
      │ │ │ -{type,external}
      │ │ │ +
      1> erlang:fun_info(fun() -> ok end, type).
      │ │ │ +{type,local}
      │ │ │ +2> erlang:fun_info(fun lists:sum/1, type).
      │ │ │ +{type,external}
      │ │ │
      │ │ │ │ │ │
      │ │ │ │ │ │

      Change

      The output of fun_to_list/1 can differ between Erlang │ │ │ implementations and may change in future versions.

      │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

      │ │ │ -
      -module(test).
      │ │ │ --export([add/1, add2/0, fun_tuple/0]).
      │ │ │ -add(A) -> fun(B) -> A + B end.
      │ │ │ -add2() -> fun add/1.
      │ │ │ -fun_tuple() -> {fun() -> 1 end, fun() -> 1 end}.
      > {fun test:add/1, test:add2()}.
      │ │ │ -{fun test:add/1,#Fun<test.1.107738983>}

      Explanation: fun test:add/1 is upgradable but test:add2() is not upgradable.

      > {test:add(1), test:add(42)}.
      │ │ │ -{#Fun<test.0.107738983>,#Fun<test.0.107738983>}

      Explanation: test:add(1) and test:add(42) has the same string representation │ │ │ -as the environment is not taken into account.

      > test:fun_tuple().
      │ │ │ -{#Fun<test.2.107738983>,#Fun<test.3.107738983>}

      Explanation: The string representations differ because the funs come from │ │ │ -different fun expressions.

      > {fun() -> 1 end, fun() -> 1 end}. >
      │ │ │ -{#Fun<erl_eval.45.97283095>,#Fun<erl_eval.45.97283095>}

      Explanation: All funs created from fun expressions of this form in uncompiled │ │ │ +

      -module(test).
      │ │ │ +-export([add/1, add2/0, fun_tuple/0]).
      │ │ │ +add(A) -> fun(B) -> A + B end.
      │ │ │ +add2() -> fun add/1.
      │ │ │ +fun_tuple() -> {fun() -> 1 end, fun() -> 1 end}.
      > {fun test:add/1, test:add2()}.
      │ │ │ +{fun test:add/1,#Fun<test.1.107738983>}

      Explanation: fun test:add/1 is upgradable but test:add2() is not upgradable.

      > {test:add(1), test:add(42)}.
      │ │ │ +{#Fun<test.0.107738983>,#Fun<test.0.107738983>}

      Explanation: test:add(1) and test:add(42) has the same string representation │ │ │ +as the environment is not taken into account.

      > test:fun_tuple().
      │ │ │ +{#Fun<test.2.107738983>,#Fun<test.3.107738983>}

      Explanation: The string representations differ because the funs come from │ │ │ +different fun expressions.

      > {fun() -> 1 end, fun() -> 1 end}. >
      │ │ │ +{#Fun<erl_eval.45.97283095>,#Fun<erl_eval.45.97283095>}

      Explanation: All funs created from fun expressions of this form in uncompiled │ │ │ code with the same arity are mapped to the same list by │ │ │ fun_to_list/1.

      │ │ │ │ │ │ │ │ │
      │ │ │ │ │ │
      │ │ │ @@ -10175,22 +10175,22 @@ │ │ │
      │ │ │ │ │ │
      -spec get() -> [{Key, Val}] when Key :: term(), Val :: term().
      │ │ │ │ │ │
      │ │ │ │ │ │

      Returns the process dictionary as a list of {Key, Val} tuples. The items in │ │ │ -the returned list can be in any order.

      For example:

      1> put(key1, merry).
      │ │ │ -2> put(key2, lambs).
      │ │ │ -3> put(key3, {are, playing}).
      │ │ │ -4> lists:sort(get()).
      │ │ │ -[{key1,merry},{key2,lambs},{key3,{are,playing}}]
      │ │ │ -5> erase().
      │ │ │ -6> get().
      │ │ │ -[]
      │ │ │ +the returned list can be in any order.

      For example:

      1> put(key1, merry).
      │ │ │ +2> put(key2, lambs).
      │ │ │ +3> put(key3, {are, playing}).
      │ │ │ +4> lists:sort(get()).
      │ │ │ +[{key1,merry},{key2,lambs},{key3,{are,playing}}]
      │ │ │ +5> erase().
      │ │ │ +6> get().
      │ │ │ +[]
      │ │ │
      │ │ │ │ │ │
      │ │ │ │ │ │ │ │ │ │ │ │

      Returns the value Val associated with Key in the process dictionary, or │ │ │ undefined if Key does not exist.

      The expected time complexity for the current implementation of this function is │ │ │ O(1) and the worst case time complexity is O(N), where N is the number of │ │ │ -items in the process dictionary.

      For example:

      1> put(key1, merry).
      │ │ │ -2> put(key2, lambs).
      │ │ │ -3> put({any, [valid, term]}, {are, playing}).
      │ │ │ -4> get({any, [valid, term]}).
      │ │ │ -{are,playing}
      │ │ │ -5> erase().
      │ │ │ -6> get({any, [valid, term]}).
      │ │ │ +items in the process dictionary.

      For example:

      1> put(key1, merry).
      │ │ │ +2> put(key2, lambs).
      │ │ │ +3> put({any, [valid, term]}, {are, playing}).
      │ │ │ +4> get({any, [valid, term]}).
      │ │ │ +{are,playing}
      │ │ │ +5> erase().
      │ │ │ +6> get({any, [valid, term]}).
      │ │ │  undefined
      │ │ │
      │ │ │ │ │ │
      │ │ │ │ │ │
      │ │ │ │ │ │ @@ -10307,22 +10307,22 @@ │ │ │ │ │ │ │ │ │

      Returns a list of all keys present in the process dictionary. The items in the │ │ │ -returned list can be in any order.

      For example:

      1> put(dog, '🐶').
      │ │ │ -2> put(cow, '🐄').
      │ │ │ -3> put(lamb, '🐑').
      │ │ │ -4> lists:sort(get_keys()).
      │ │ │ -[cow,dog,lamb]
      │ │ │ -5> erase().
      │ │ │ -6> get_keys().
      │ │ │ -[]
      │ │ │ +returned list can be in any order.

      For example:

      1> put(dog, '🐶').
      │ │ │ +2> put(cow, '🐄').
      │ │ │ +3> put(lamb, '🐑').
      │ │ │ +4> lists:sort(get_keys()).
      │ │ │ +[cow,dog,lamb]
      │ │ │ +5> erase().
      │ │ │ +6> get_keys().
      │ │ │ +[]
      │ │ │
      │ │ │ │ │ │
      │ │ │ │ │ │
      │ │ │ │ │ │ │ │ │ @@ -10343,25 +10343,25 @@ │ │ │ │ │ │ │ │ │

      Returns a list of keys that are associated with the value Val in the process │ │ │ -dictionary. The items in the returned list can be in any order.

      For example:

      1> put(allosaurus, '🦕').
      │ │ │ -2> put(brachiosaurus, '🦕').
      │ │ │ -3> put(carnotaurus, '🦕').
      │ │ │ -4> put(diplodocus, '🦕').
      │ │ │ -5> put(euoplocephalus, '🦕').
      │ │ │ -6> put(fox, '🦊').
      │ │ │ -7> lists:sort(get_keys('🦕')).
      │ │ │ -[allosaurus,brachiosaurus,carnotaurus,diplodocus,euoplocephalus]
      │ │ │ -8> erase().
      │ │ │ -9> get_keys('🦕').
      │ │ │ -[]
      │ │ │ +dictionary. The items in the returned list can be in any order.

      For example:

      1> put(allosaurus, '🦕').
      │ │ │ +2> put(brachiosaurus, '🦕').
      │ │ │ +3> put(carnotaurus, '🦕').
      │ │ │ +4> put(diplodocus, '🦕').
      │ │ │ +5> put(euoplocephalus, '🦕').
      │ │ │ +6> put(fox, '🦊').
      │ │ │ +7> lists:sort(get_keys('🦕')).
      │ │ │ +[allosaurus,brachiosaurus,carnotaurus,diplodocus,euoplocephalus]
      │ │ │ +8> erase().
      │ │ │ +9> get_keys('🦕').
      │ │ │ +[]
      │ │ │
      │ │ │ │ │ │
      │ │ │ │ │ │
      │ │ │ │ │ │ │ │ │ @@ -10456,15 +10456,15 @@ │ │ │ │ │ │ │ │ │ │ │ │ -

      Equivalent to calling halt(0, []).

      For example:

      > halt().
      │ │ │ +

      Equivalent to calling halt(0, []).

      For example:

      > halt().
      │ │ │  os_prompt%
      │ │ │
      │ │ │ │ │ │
      │ │ │ │ │ │
      │ │ │ │ │ │ @@ -10487,15 +10487,15 @@ │ │ │ │ │ │
      -spec halt(Status :: non_neg_integer()) -> no_return();
      │ │ │            (Abort :: abort) -> no_return();
      │ │ │            (CrashDumpSlogan :: string()) -> no_return().
      │ │ │ │ │ │
      │ │ │ │ │ │ -

      Equivalent to calling halt(HaltType, []).

      For example:

      > halt(17).
      │ │ │ +

      Equivalent to calling halt(HaltType, []).

      For example:

      > halt(17).
      │ │ │  os_prompt% echo $?
      │ │ │  17
      │ │ │  os_prompt%
      │ │ │
      │ │ │ │ │ │
      │ │ │ │ │ │ @@ -10522,15 +10522,15 @@ │ │ │ │ │ │
      -spec halt(Status :: non_neg_integer(), Options :: halt_options()) -> no_return();
      │ │ │            (Abort :: abort, Options :: halt_options()) -> no_return();
      │ │ │            (CrashDumpSlogan :: string(), Options :: halt_options()) -> no_return().
      │ │ │ │ │ │ │ │ │ │ │ │ -

      Halt the runtime system.

      • halt(Status :: non_neg_integer(), Options :: halt_options())

        Halt the runtime system with status code Status.

        Note

        On many platforms, the OS supports only status codes 0-255. A too large │ │ │ +

        Halt the runtime system.

        • halt(Status :: non_neg_integer(), Options :: halt_options())

          Halt the runtime system with status code Status.

          Note

          On many platforms, the OS supports only status codes 0-255. A too large │ │ │ status code is truncated by clearing the high bits.

          Currently the following options are valid:

          • {flush, EnableFlushing} - If EnableFlushing equals │ │ │ true, which also is the default behavior, the runtime system will perform │ │ │ the following operations before terminating:

            • Flush all outstanding output.
            • Send all Erlang ports exit signals and wait for them to exit.
            • Wait for all async threads to complete all outstanding async jobs.
            • Call all installed NIF on halt callbacks.
            • Wait for all ongoing │ │ │ NIF calls with the delay halt setting enabled │ │ │ to return.
            • Call all installed atexit/on_exit callbacks.

            If EnableFlushing equals false, the runtime system will terminate │ │ │ immediately without performing any of the above listed operations.

            Change

            Runtime systems prior to OTP 26.0 called all installed atexit/on_exit │ │ │ callbacks also when flush was disabled, but as of OTP 26.0 this is no │ │ │ @@ -10539,18 +10539,18 @@ │ │ │ termination of the runtime system. Timeout is in milliseconds. The default │ │ │ value is determined by the the erl +zhft <Timeout> │ │ │ command line flag.

            If flushing has been ongoing for Timeout milliseconds, flushing operations │ │ │ will be interrupted and the runtime system will immediately be terminated │ │ │ with the exit code 255. If flushing is not enabled, the timeout will have │ │ │ no effect on the system.

            See also the erl +zhft <Timeout> command line flag. │ │ │ Note that the shortest timeout set by the command line flag and the │ │ │ -flush_timeout option will be the actual timeout value in effect.

            Since: OTP 27.0

        • halt(Abort :: abort, Options :: halt_options())

          Halt the Erlang runtime system by aborting and produce a core dump if core │ │ │ +flush_timeout option will be the actual timeout value in effect.

          Since: OTP 27.0

      • halt(Abort :: abort, Options :: halt_options())

        Halt the Erlang runtime system by aborting and produce a core dump if core │ │ │ dumping has been enabled in the environment that the runtime system is │ │ │ executing in.

        Note

        The {flush, boolean()} option will be ignored, and │ │ │ -flushing will be disabled.

      • halt(CrashDumpSlogan :: string(), Options :: halt_options())

        Halt the Erlang runtime system and generate an │ │ │ +flushing will be disabled.

    • halt(CrashDumpSlogan :: string(), Options :: halt_options())

      Halt the Erlang runtime system and generate an │ │ │ Erlang crash dump. The string CrashDumpSlogan will be used │ │ │ as slogan in the Erlang crash dump created. The slogan will be trunkated if │ │ │ CrashDumpSlogan is longer than 1023 characters.

      Note

      The {flush, boolean()} option will be ignored, and │ │ │ flushing will be disabled.

      Change

      Behavior changes compared to earlier versions:

      • Before OTP 24.2, the slogan was truncated if CrashDumpSlogan was longer │ │ │ than 200 characters. Now it will be truncated if longer than 1023 │ │ │ characters.
      • Before OTP 20.1, only code points in the range 0-255 were accepted in the │ │ │ slogan. Now any Unicode string is valid.
    │ │ │ @@ -10585,19 +10585,19 @@ │ │ │ │ │ │

    Returns the first element of List.

    It works with improper lists.

    Failure: badarg if List is the empty list [].

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> hd([1,2,3,4,5]).
    │ │ │ +
    1> hd([1,2,3,4,5]).
    │ │ │  1
    │ │ │ -2> hd([first, second, third, so_on | improper_end]).
    │ │ │ +2> hd([first, second, third, so_on | improper_end]).
    │ │ │  first
    │ │ │ -3> hd([]).
    │ │ │ +3> hd([]).
    │ │ │  ** exception error: bad argument
    │ │ │       in function  hd/1
    │ │ │          called as hd([])
    │ │ │          *** argument 1: not a nonempty list
    │ │ │ │ │ │ │ │ │
    │ │ │ @@ -10708,16 +10708,16 @@ │ │ │ Tuple1.

    All elements from position Index and upwards are pushed one step │ │ │ higher in the new tuple Tuple2.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> erlang:insert_element(2, {one, two, three}, new).
    │ │ │ -{one,new,two,three}
    │ │ │ +
    1> erlang:insert_element(2, {one, two, three}, new).
    │ │ │ +{one,new,two,three}
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -10745,16 +10745,16 @@ │ │ │ │ │ │

    Returns a binary corresponding to the text representation of Integer.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> integer_to_binary(77).
    │ │ │ -<<"77">>
    │ │ │ +
    1> integer_to_binary(77).
    │ │ │ +<<"77">>
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -10783,16 +10783,16 @@ │ │ │

    Returns a binary corresponding to the text representation of Integer in base │ │ │ Base.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> integer_to_binary(1023, 16).
    │ │ │ -<<"3FF">>
    │ │ │ +
    1> integer_to_binary(1023, 16).
    │ │ │ +<<"3FF">>
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -10818,15 +10818,15 @@ │ │ │ │ │ │

    Returns a string corresponding to the text representation of Integer.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> integer_to_list(77).
    │ │ │ +
    1> integer_to_list(77).
    │ │ │  "77"
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10854,15 +10854,15 @@ │ │ │

    Returns a string corresponding to the text representation of Integer in base │ │ │ Base.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> integer_to_list(1023, 16).
    │ │ │ +
    1> integer_to_list(1023, 16).
    │ │ │  "3FF"
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10890,15 +10890,15 @@ │ │ │

    Returns the size in bytes of the binary that would result from │ │ │ iolist_to_binary(Item).

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> iolist_size([1,2|<<3,4>>]).
    │ │ │ +
    1> iolist_size([1,2|<<3,4>>]).
    │ │ │  4
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -10926,22 +10926,22 @@ │ │ │

    Returns a binary constructed from the integers and binaries in │ │ │ IoListOrBinary.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> Bin1 = <<1,2,3>>.
    │ │ │ -<<1,2,3>>
    │ │ │ -2> Bin2 = <<4,5>>.
    │ │ │ -<<4,5>>
    │ │ │ -3> Bin3 = <<6>>.
    │ │ │ -<<6>>
    │ │ │ -4> iolist_to_binary([Bin1,1,[2,3,Bin2],4|Bin3]).
    │ │ │ -<<1,2,3,1,2,3,4,5,4,6>>
    │ │ │ +
    1> Bin1 = <<1,2,3>>.
    │ │ │ +<<1,2,3>>
    │ │ │ +2> Bin2 = <<4,5>>.
    │ │ │ +<<4,5>>
    │ │ │ +3> Bin3 = <<6>>.
    │ │ │ +<<6>>
    │ │ │ +4> iolist_to_binary([Bin1,1,[2,3,Bin2],4|Bin3]).
    │ │ │ +<<1,2,3,1,2,3,4,5,4,6>>
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -10973,27 +10973,27 @@ │ │ │ efficient message passing. The advantage of using this function over │ │ │ iolist_to_binary/1 is that it does not need to copy off-heap binaries.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -

    If you pass small binaries and integers, it works like iolist_to_binary/1.

    1> Bin1 = <<1,2,3>>.
    │ │ │ -<<1,2,3>>
    │ │ │ -2> Bin2 = <<4,5>>.
    │ │ │ -<<4,5>>
    │ │ │ -3> Bin3 = <<6>>.
    │ │ │ -<<6>>
    │ │ │ -4> erlang:iolist_to_iovec([Bin1,1,[2,3,Bin2],4|Bin3]).
    │ │ │ -[<<1,2,3,1,2,3,4,5,4,6>>]

    If you pass larger binaries, they are split and returned in a form │ │ │ -optimized for calling the C function writev().

    1> erlang:iolist_to_iovec([<<1>>,<<2:8096>>,<<3:8096>>]).
    │ │ │ -[<<1>>,
    │ │ │ - <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    │ │ │ -   ...>>,
    │ │ │ - <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...>>]
    │ │ │ +

    If you pass small binaries and integers, it works like iolist_to_binary/1.

    1> Bin1 = <<1,2,3>>.
    │ │ │ +<<1,2,3>>
    │ │ │ +2> Bin2 = <<4,5>>.
    │ │ │ +<<4,5>>
    │ │ │ +3> Bin3 = <<6>>.
    │ │ │ +<<6>>
    │ │ │ +4> erlang:iolist_to_iovec([Bin1,1,[2,3,Bin2],4|Bin3]).
    │ │ │ +[<<1,2,3,1,2,3,4,5,4,6>>]

    If you pass larger binaries, they are split and returned in a form │ │ │ +optimized for calling the C function writev().

    1> erlang:iolist_to_iovec([<<1>>,<<2:8096>>,<<3:8096>>]).
    │ │ │ +[<<1>>,
    │ │ │ + <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
    │ │ │ +   ...>>,
    │ │ │ + <<0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...>>]
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ │ │ │ @@ -11051,17 +11051,17 @@ │ │ │ │ │ │

    Returns true if Term is an atom; otherwise, returns false.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> is_atom(42).
    │ │ │ +
    1> is_atom(42).
    │ │ │  false
    │ │ │ -2> is_atom(ok).
    │ │ │ +2> is_atom(ok).
    │ │ │  true
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11090,19 +11090,19 @@ │ │ │ │ │ │

    Returns true if Term is a binary; otherwise, returns false.

    A binary always contains a complete number of bytes.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> is_binary(42).
    │ │ │ +
    1> is_binary(42).
    │ │ │  false
    │ │ │ -2> is_binary(<<1,2,3>>).
    │ │ │ +2> is_binary(<<1,2,3>>).
    │ │ │  true
    │ │ │ -3> is_binary(<<7:12>>).
    │ │ │ +3> is_binary(<<7:12>>).
    │ │ │  false
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11131,19 +11131,19 @@ │ │ │ │ │ │

    Returns true if Term is a bitstring (including a binary); otherwise, returns false.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> is_bitstring(42).
    │ │ │ +
    1> is_bitstring(42).
    │ │ │  false
    │ │ │ -2> is_bitstring(<<1,2,3>>).
    │ │ │ +2> is_bitstring(<<1,2,3>>).
    │ │ │  true
    │ │ │ -3> is_bitstring(<<7:12>>).
    │ │ │ +3> is_bitstring(<<7:12>>).
    │ │ │  true
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11172,21 +11172,21 @@ │ │ │ │ │ │

    Returns true if Term is the atom true or false; otherwise, returns false.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> is_boolean(true).
    │ │ │ +
    1> is_boolean(true).
    │ │ │  true
    │ │ │ -2> is_boolean(false).
    │ │ │ +2> is_boolean(false).
    │ │ │  true
    │ │ │ -3> is_boolean(ok).
    │ │ │ +3> is_boolean(ok).
    │ │ │  false
    │ │ │ -4> is_boolean(42).
    │ │ │ +4> is_boolean(42).
    │ │ │  false
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11213,17 +11213,17 @@ │ │ │

    This BIF is useful for builders of cross-reference tools.

    Returns true if Module:Function/Arity is a BIF implemented in C, otherwise │ │ │ false.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples: │ │ │

    │ │ │ -
    1> erlang:is_builtin(lists, keyfind, 3).
    │ │ │ +
    1> erlang:is_builtin(lists, keyfind, 3).
    │ │ │  true
    │ │ │ -2> erlang:is_builtin(lists, reverse, 1).
    │ │ │ +2> erlang:is_builtin(lists, reverse, 1).
    │ │ │  false
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │ @@ -11252,19 +11252,19 @@ │ │ │ │ │ │

    Returns true if Term is a floating point number; otherwise, returns false.

    │ │ │ │ │ │ │ │ │ │ │ │ Examples │ │ │

    │ │ │ -
    1> is_float(42).
    │ │ │ +
    1> is_float(42).
    │ │ │  false
    │ │ │ -2> is_float(42.0).
    │ │ │ +2> is_float(42.0).
    │ │ │  true
    │ │ │ -3> is_float(zero).
    │ │ │ +3> is_float(zero).
    │ │ │  false
    │ │ │
    │ │ │ │ │ │
    │ │ │ │ │ │