--- /srv/rebuilderd/tmp/rebuilderdxWd4eS/inputs/erlang-doc_27.3.4.11+dfsg-3_all.deb +++ /srv/rebuilderd/tmp/rebuilderdxWd4eS/out/erlang-doc_27.3.4.11+dfsg-3_all.deb ├── file list │ @@ -1,3 +1,3 @@ │ -rw-r--r-- 0 0 0 4 2026-05-05 12:33:17.000000 debian-binary │ --rw-r--r-- 0 0 0 40648 2026-05-05 12:33:17.000000 control.tar.xz │ --rw-r--r-- 0 0 0 21043352 2026-05-05 12:33:17.000000 data.tar.xz │ +-rw-r--r-- 0 0 0 40636 2026-05-05 12:33:17.000000 control.tar.xz │ +-rw-r--r-- 0 0 0 21042900 2026-05-05 12:33:17.000000 data.tar.xz ├── control.tar.xz │ ├── control.tar │ │ ├── ./md5sums │ │ │ ├── ./md5sums │ │ │ │┄ Files differ │ │ │ ├── line order │ │ │ │ @@ -247,15 +247,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/dist/search_data-8B3C849F.js │ │ │ │ +usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/dist/search_data-FC9EDC58.js │ │ │ │ usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/dist/sidebar_items-E25F4A7E.js │ │ │ │ usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/driver.html │ │ │ │ usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/driver_entry.html │ │ │ │ usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/epmd_cmd.html │ │ │ │ usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/erl_cmd.html │ │ │ │ usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/erl_dist_protocol.html │ │ │ │ usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/erl_driver.html │ │ │ │ @@ -558,15 +558,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/dist/search_data-67110493.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/dist/search_data-0063A89A.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/dist/sidebar_items-61D5D22A.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/notes.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/search.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/chunks/edoc.chunk │ │ │ │ usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/chunks/edoc_cli.chunk │ │ │ │ usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/chunks/edoc_data.chunk │ │ │ │ @@ -673,15 +673,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/dist/search_data-1F14090C.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/dist/search_data-D82831C4.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/dist/sidebar_items-8A5CCEF3.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/ei.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/ei_connect.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/ei_global.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/ei_users_guide.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/erl_call_cmd.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/erl_interface.epub │ │ │ │ @@ -781,15 +781,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/search_data-D71AAFA6.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/search_data-FF6A1601.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/sidebar_items-B6B07F6E.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/ftp.epub │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/ftp.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/ftp_client.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/introduction.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/notes.html │ │ │ │ @@ -1001,15 +1001,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/dist/search_data-9CA2735F.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/dist/search_data-45DA08B4.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/dist/sidebar_items-FCA6C10D.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/eep48_chapter.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/erl_boot_server.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/erl_ddll.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/erl_epmd.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/erpc.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/error_handler.html │ │ │ │ @@ -1169,15 +1169,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/search_data-C92E6517.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/search_data-FFC02264.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/sidebar_items-6D9D41B7.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/etop.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/etop_ug.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/introduction_ug.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/notes.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/observer.epub │ │ │ │ @@ -1362,15 +1362,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/search_data-A1188F1F.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/search_data-1DC1D27C.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/sidebar_items-E5CACCF4.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dtrace.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dyntrace.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/instrument.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/lttng.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/msacc.html ├── data.tar.xz │ ├── data.tar │ │ ├── file list │ │ │ @@ -137,15 +137,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 293 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/doc/ssh.html │ │ │ -rw-r--r-- 0 root (0) root (0) 294 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/doc/ssl.html │ │ │ -rw-r--r-- 0 root (0) root (0) 295 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/doc/stdlib.html │ │ │ -rw-r--r-- 0 root (0) root (0) 301 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/doc/syntax_tools.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/doc/system/ │ │ │ -rw-r--r-- 0 root (0) root (0) 2286 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/doc/system/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 5651 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/doc/system/404.html │ │ │ --rw-r--r-- 0 root (0) root (0) 661070 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/doc/system/Erlang System Documentation.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 661082 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/doc/system/Erlang System Documentation.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 53766 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/doc/system/applications.html │ │ │ -rw-r--r-- 0 root (0) root (0) 109989 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/doc/system/appup_cookbook.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 7982 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/ballpoint-pen.svg │ │ │ -rw-r--r-- 0 root (0) root (0) 2284 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/dist1.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 5214 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/dist2.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 5007 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/dist3.gif │ │ │ @@ -304,15 +304,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5820 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5612 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5552 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 2383296 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/dist/search_data-8B3C849F.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 2383296 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/dist/search_data-FC9EDC58.js │ │ │ -rw-r--r-- 0 root (0) root (0) 100223 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/dist/sidebar_items-E25F4A7E.js │ │ │ -rw-r--r-- 0 root (0) root (0) 141186 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/driver.html │ │ │ -rw-r--r-- 0 root (0) root (0) 46982 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/driver_entry.html │ │ │ -rw-r--r-- 0 root (0) root (0) 20642 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/epmd_cmd.html │ │ │ -rw-r--r-- 0 root (0) root (0) 124837 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/erl_cmd.html │ │ │ -rw-r--r-- 0 root (0) root (0) 94002 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/erl_dist_protocol.html │ │ │ -rw-r--r-- 0 root (0) root (0) 210853 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/erl_driver.html │ │ │ @@ -351,15 +351,15 @@ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1060 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6010 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/404.html │ │ │ -rw-r--r-- 0 root (0) root (0) 6692 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/api-reference.html │ │ │ --rw-r--r-- 0 root (0) root (0) 96765 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/asn1.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 96763 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/asn1.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 142696 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/asn1_getting_started.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9328 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/asn1_introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7454 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/asn1_overview.html │ │ │ -rw-r--r-- 0 root (0) root (0) 79237 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/asn1_spec.html │ │ │ -rw-r--r-- 0 root (0) root (0) 35477 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/asn1ct.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1340 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/assets/exclusive_Win_But.gif │ │ │ @@ -397,15 +397,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 10672 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/api-reference.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 4963 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/assets/config.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 10726 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/assets/html_logs.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/assets/logo.png │ │ │ -rw-r--r-- 0 root (0) root (0) 9561 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/assets/tc_execution.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 21795 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/basics_chapter.html │ │ │ --rw-r--r-- 0 root (0) root (0) 399413 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/common_test.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 399401 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/common_test.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 7502 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/common_test_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 59626 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/config_file_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25541 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/cover_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 182868 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/ct.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12310 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/ct_cover.html │ │ │ -rw-r--r-- 0 root (0) root (0) 30032 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/ct_ftp.html │ │ │ -rw-r--r-- 0 root (0) root (0) 77362 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/ct_hooks.html │ │ │ @@ -466,15 +466,15 @@ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-8.6.1.4/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-8.6.1.4/doc/html/assets/logo.png │ │ │ -rw-r--r-- 0 root (0) root (0) 24226 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-8.6.1.4/doc/html/beam_ssa.html │ │ │ -rw-r--r-- 0 root (0) root (0) 450809 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-8.6.1.4/doc/html/cerl.html │ │ │ -rw-r--r-- 0 root (0) root (0) 28701 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-8.6.1.4/doc/html/cerl_clauses.html │ │ │ -rw-r--r-- 0 root (0) root (0) 28995 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-8.6.1.4/doc/html/cerl_trees.html │ │ │ -rw-r--r-- 0 root (0) root (0) 84370 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-8.6.1.4/doc/html/compile.html │ │ │ --rw-r--r-- 0 root (0) root (0) 182720 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-8.6.1.4/doc/html/compiler.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 182709 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-8.6.1.4/doc/html/compiler.epub │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-8.6.1.4/doc/html/dist/ │ │ │ -rw-r--r-- 0 root (0) root (0) 19268 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-8.6.1.4/doc/html/dist/handlebars.runtime-WZY45QD2.js │ │ │ -rw-r--r-- 0 root (0) root (0) 33556 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-8.6.1.4/doc/html/dist/handlebars.templates-W6ARKZMU.js │ │ │ -rw-r--r-- 0 root (0) root (0) 70576 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-8.6.1.4/doc/html/dist/html-RFEC7MEN.js │ │ │ -rw-r--r-- 0 root (0) root (0) 67452 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-8.6.1.4/doc/html/dist/html-erlang-V4OM2JZP.css │ │ │ -rw-r--r-- 0 root (0) root (0) 17664 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-8.6.1.4/doc/html/dist/inconsolata-latin-400-normal-5NIOBTAW.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 17960 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-8.6.1.4/doc/html/dist/inconsolata-latin-700-normal-YRJBXG74.woff2 │ │ │ @@ -546,15 +546,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 21770 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/assets/cond_break_dialog.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 13532 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/assets/function_break_dialog.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 28924 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/assets/interpret.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 14414 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/assets/line_break_dialog.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/assets/logo.png │ │ │ -rw-r--r-- 0 root (0) root (0) 40742 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/assets/monitor.jpg │ │ │ -rw-r--r-- 0 root (0) root (0) 34504 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/assets/view.jpg │ │ │ --rw-r--r-- 0 root (0) root (0) 219463 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/debugger.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 219460 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/debugger.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 13135 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/debugger.html │ │ │ -rw-r--r-- 0 root (0) root (0) 52048 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/debugger_chapter.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/dist/ │ │ │ -rw-r--r-- 0 root (0) root (0) 19268 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/dist/handlebars.runtime-WZY45QD2.js │ │ │ -rw-r--r-- 0 root (0) root (0) 33556 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/dist/handlebars.templates-W6ARKZMU.js │ │ │ -rw-r--r-- 0 root (0) root (0) 70576 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/dist/html-RFEC7MEN.js │ │ │ -rw-r--r-- 0 root (0) root (0) 67452 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/debugger-5.5.0.1/doc/html/dist/html-erlang-V4OM2JZP.css │ │ │ @@ -583,15 +583,15 @@ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 921 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6028 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/404.html │ │ │ -rw-r--r-- 0 root (0) root (0) 6794 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/api-reference.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/assets/logo.png │ │ │ --rw-r--r-- 0 root (0) root (0) 66382 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dialyzer.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 66391 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dialyzer.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 53654 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dialyzer.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25902 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dialyzer_chapter.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/ │ │ │ -rw-r--r-- 0 root (0) root (0) 19268 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/handlebars.runtime-WZY45QD2.js │ │ │ -rw-r--r-- 0 root (0) root (0) 33556 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/handlebars.templates-W6ARKZMU.js │ │ │ -rw-r--r-- 0 root (0) root (0) 70576 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/html-RFEC7MEN.js │ │ │ -rw-r--r-- 0 root (0) root (0) 67452 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/html-erlang-V4OM2JZP.css │ │ │ @@ -618,15 +618,15 @@ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1143 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6034 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/404.html │ │ │ -rw-r--r-- 0 root (0) root (0) 8222 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/api-reference.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/assets/logo.png │ │ │ --rw-r--r-- 0 root (0) root (0) 144836 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/diameter.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 144839 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/diameter.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 256977 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/diameter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 57599 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/diameter_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 29032 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/diameter_codec.html │ │ │ -rw-r--r-- 0 root (0) root (0) 32274 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/diameter_dict.html │ │ │ -rw-r--r-- 0 root (0) root (0) 6784 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/diameter_examples.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9532 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/diameter_intro.html │ │ │ -rw-r--r-- 0 root (0) root (0) 21968 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/diameter_make.html │ │ │ @@ -651,15 +651,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5820 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5612 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5552 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 309796 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/dist/search_data-67110493.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 309796 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/dist/search_data-0063A89A.js │ │ │ -rw-r--r-- 0 root (0) root (0) 14759 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/dist/sidebar_items-61D5D22A.js │ │ │ -rw-r--r-- 0 root (0) root (0) 268 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 100653 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5950 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/edoc-1.3.2/doc/chunks/ │ │ │ @@ -752,15 +752,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.14.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5820 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.14.1/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5612 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.14.1/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5552 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.14.1/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.14.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 24651 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.14.1/doc/html/dist/search_data-7A421979.js │ │ │ -rw-r--r-- 0 root (0) root (0) 6047 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.14.1/doc/html/dist/sidebar_items-5C2028D5.js │ │ │ --rw-r--r-- 0 root (0) root (0) 33172 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.14.1/doc/html/eldap.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 33171 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.14.1/doc/html/eldap.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 94819 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.14.1/doc/html/eldap.html │ │ │ -rw-r--r-- 0 root (0) root (0) 266 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.14.1/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25611 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.14.1/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5935 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/eldap-1.2.14.1/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/ │ │ │ @@ -782,22 +782,22 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5820 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5612 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5552 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 193099 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/dist/search_data-1F14090C.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 193099 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/dist/search_data-D82831C4.js │ │ │ -rw-r--r-- 0 root (0) root (0) 15936 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/dist/sidebar_items-8A5CCEF3.js │ │ │ -rw-r--r-- 0 root (0) root (0) 138622 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/ei.html │ │ │ -rw-r--r-- 0 root (0) root (0) 132616 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/ei_connect.html │ │ │ -rw-r--r-- 0 root (0) root (0) 14468 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/ei_global.html │ │ │ -rw-r--r-- 0 root (0) root (0) 50861 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/ei_users_guide.html │ │ │ -rw-r--r-- 0 root (0) root (0) 22688 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/erl_call_cmd.html │ │ │ --rw-r--r-- 0 root (0) root (0) 99321 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/erl_interface.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 99319 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/erl_interface.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 272 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 110953 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5565 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1332 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/.build │ │ │ @@ -832,15 +832,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5820 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5612 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5552 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 80722 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/dist/search_data-07FF68FB.js │ │ │ -rw-r--r-- 0 root (0) root (0) 9243 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/dist/sidebar_items-639C3385.js │ │ │ --rw-r--r-- 0 root (0) root (0) 302425 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/et.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 302435 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/et.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 22899 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/et.html │ │ │ -rw-r--r-- 0 root (0) root (0) 57168 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/et_collector.html │ │ │ -rw-r--r-- 0 root (0) root (0) 52828 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/et_desc.html │ │ │ -rw-r--r-- 0 root (0) root (0) 100672 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/et_examples.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9916 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/et_intro.html │ │ │ -rw-r--r-- 0 root (0) root (0) 20457 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/et_selector.html │ │ │ -rw-r--r-- 0 root (0) root (0) 45778 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/et_tutorial.html │ │ │ @@ -905,17 +905,17 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5820 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5612 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5552 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 29403 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/search_data-D71AAFA6.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 29403 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/search_data-FF6A1601.js │ │ │ -rw-r--r-- 0 root (0) root (0) 5270 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/dist/sidebar_items-B6B07F6E.js │ │ │ --rw-r--r-- 0 root (0) root (0) 33163 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/ftp.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 33164 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/ftp.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 82127 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/ftp.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12856 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/ftp_client.html │ │ │ -rw-r--r-- 0 root (0) root (0) 261 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7162 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 22515 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5914 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.4/ │ │ │ @@ -951,15 +951,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 11421 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.4/doc/html/http_uri.html │ │ │ -rw-r--r-- 0 root (0) root (0) 91302 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.4/doc/html/httpc.html │ │ │ -rw-r--r-- 0 root (0) root (0) 121825 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.4/doc/html/httpd.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12136 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.4/doc/html/httpd_custom_api.html │ │ │ -rw-r--r-- 0 root (0) root (0) 13448 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.4/doc/html/httpd_socket.html │ │ │ -rw-r--r-- 0 root (0) root (0) 45124 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.4/doc/html/httpd_util.html │ │ │ -rw-r--r-- 0 root (0) root (0) 265 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.4/doc/html/index.html │ │ │ --rw-r--r-- 0 root (0) root (0) 152860 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.4/doc/html/inets.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 152856 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.4/doc/html/inets.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 25717 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.4/doc/html/inets.html │ │ │ -rw-r--r-- 0 root (0) root (0) 8659 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.4/doc/html/inets_services.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7466 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.4/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 21300 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.4/doc/html/mod_alias.html │ │ │ -rw-r--r-- 0 root (0) root (0) 83168 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.4/doc/html/mod_auth.html │ │ │ -rw-r--r-- 0 root (0) root (0) 19815 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.4/doc/html/mod_esi.html │ │ │ -rw-r--r-- 0 root (0) root (0) 37282 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.4/doc/html/mod_security.html │ │ │ @@ -1149,15 +1149,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5820 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5612 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5552 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 1225649 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/dist/search_data-9CA2735F.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 1225649 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/dist/search_data-45DA08B4.js │ │ │ -rw-r--r-- 0 root (0) root (0) 115768 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/dist/sidebar_items-FCA6C10D.js │ │ │ -rw-r--r-- 0 root (0) root (0) 21084 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/eep48_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 15895 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/erl_boot_server.html │ │ │ -rw-r--r-- 0 root (0) root (0) 85756 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/erl_ddll.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25156 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/erl_epmd.html │ │ │ -rw-r--r-- 0 root (0) root (0) 111330 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/erpc.html │ │ │ -rw-r--r-- 0 root (0) root (0) 15177 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/error_handler.html │ │ │ @@ -1169,15 +1169,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 57284 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/global.html │ │ │ -rw-r--r-- 0 root (0) root (0) 37262 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/global_group.html │ │ │ -rw-r--r-- 0 root (0) root (0) 24987 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/heart.html │ │ │ -rw-r--r-- 0 root (0) root (0) 267 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 184632 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/inet.html │ │ │ -rw-r--r-- 0 root (0) root (0) 88431 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/inet_res.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7733 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/introduction_chapter.html │ │ │ --rw-r--r-- 0 root (0) root (0) 793526 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/kernel.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 793528 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/kernel.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 43434 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/kernel_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 188628 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/logger.html │ │ │ -rw-r--r-- 0 root (0) root (0) 108846 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/logger_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 70817 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/logger_cookbook.html │ │ │ -rw-r--r-- 0 root (0) root (0) 15657 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/logger_disk_log_h.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25632 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/logger_filters.html │ │ │ -rw-r--r-- 0 root (0) root (0) 34207 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/logger_formatter.html │ │ │ @@ -1229,15 +1229,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5820 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5612 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5552 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 200508 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/dist/search_data-7DE65072.js │ │ │ -rw-r--r-- 0 root (0) root (0) 33294 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/dist/sidebar_items-017D32EE.js │ │ │ -rw-r--r-- 0 root (0) root (0) 266 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/index.html │ │ │ --rw-r--r-- 0 root (0) root (0) 182331 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/megaco.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 182341 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/megaco.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 199469 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/megaco.html │ │ │ -rw-r--r-- 0 root (0) root (0) 13686 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/megaco_architecture.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9144 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/megaco_codec_meas.html │ │ │ -rw-r--r-- 0 root (0) root (0) 23106 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/megaco_codec_mstone1.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9748 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/megaco_codec_mstone2.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9720 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/megaco_codec_transform.html │ │ │ -rw-r--r-- 0 root (0) root (0) 18682 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/megaco_debug.html │ │ │ @@ -1284,15 +1284,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5820 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5612 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5552 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 376330 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/dist/search_data-4C102A02.js │ │ │ -rw-r--r-- 0 root (0) root (0) 24634 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/dist/sidebar_items-A19B2882.js │ │ │ -rw-r--r-- 0 root (0) root (0) 267 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/index.html │ │ │ --rw-r--r-- 0 root (0) root (0) 222170 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/mnesia.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 222184 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/mnesia.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 320928 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/mnesia.html │ │ │ -rw-r--r-- 0 root (0) root (0) 45474 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/mnesia_app_a.html │ │ │ -rw-r--r-- 0 root (0) root (0) 87801 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/mnesia_app_b.html │ │ │ -rw-r--r-- 0 root (0) root (0) 46066 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/mnesia_app_c.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9875 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/mnesia_chap1.html │ │ │ -rw-r--r-- 0 root (0) root (0) 109262 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/mnesia_chap2.html │ │ │ -rw-r--r-- 0 root (0) root (0) 51400 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/mnesia_chap3.html │ │ │ @@ -1332,22 +1332,22 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5820 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5612 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5552 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 146200 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/search_data-C92E6517.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 146200 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/search_data-FFC02264.js │ │ │ -rw-r--r-- 0 root (0) root (0) 12765 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/dist/sidebar_items-6D9D41B7.js │ │ │ -rw-r--r-- 0 root (0) root (0) 17966 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/etop.html │ │ │ -rw-r--r-- 0 root (0) root (0) 15746 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/etop_ug.html │ │ │ -rw-r--r-- 0 root (0) root (0) 265 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7350 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/introduction_ug.html │ │ │ -rw-r--r-- 0 root (0) root (0) 71142 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 116803 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/observer.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 116805 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/observer.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 13905 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/observer.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7238 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/observer_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 23494 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/observer_ug.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5941 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 111981 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/ttb.html │ │ │ -rw-r--r-- 0 root (0) root (0) 165996 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/ttb_ug.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/ │ │ │ @@ -1381,15 +1381,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 76343 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/search_data-D78563F6.js │ │ │ -rw-r--r-- 0 root (0) root (0) 7406 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/sidebar_items-19ECDBA9.js │ │ │ -rw-r--r-- 0 root (0) root (0) 13859 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/error_handling.html │ │ │ -rw-r--r-- 0 root (0) root (0) 51373 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/getting_started.html │ │ │ -rw-r--r-- 0 root (0) root (0) 261 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 8466 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 57071 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 67281 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/odbc.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 67289 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/odbc.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 76660 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/odbc.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5917 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 952 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6019 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/doc/html/404.html │ │ │ @@ -1418,15 +1418,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 69231 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/doc/html/dist/search_data-D20C2403.js │ │ │ -rw-r--r-- 0 root (0) root (0) 7923 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/doc/html/dist/sidebar_items-CB1A13C5.js │ │ │ -rw-r--r-- 0 root (0) root (0) 265 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 31362 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/doc/html/memsup.html │ │ │ -rw-r--r-- 0 root (0) root (0) 57045 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 14802 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/doc/html/nteventlog.html │ │ │ --rw-r--r-- 0 root (0) root (0) 50284 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/doc/html/os_mon.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 50280 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/doc/html/os_mon.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 9983 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/doc/html/os_mon_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 22952 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/doc/html/os_sup.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5935 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 890 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/.build │ │ │ @@ -1453,15 +1453,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5552 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 55131 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/dist/search_data-5B3B164D.js │ │ │ -rw-r--r-- 0 root (0) root (0) 5679 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/dist/sidebar_items-E70C9F62.js │ │ │ -rw-r--r-- 0 root (0) root (0) 266 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 55824 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/leex.html │ │ │ -rw-r--r-- 0 root (0) root (0) 37754 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 44439 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/parsetools.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 44438 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/parsetools.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 5950 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 67892 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/yecc.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.2/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.2/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.2/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 952 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.2/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6049 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.2/doc/html/404.html │ │ │ @@ -1486,15 +1486,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5612 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.2/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5552 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.2/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.2/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 145962 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.2/doc/html/dist/search_data-951B1B32.js │ │ │ -rw-r--r-- 0 root (0) root (0) 16526 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.2/doc/html/dist/sidebar_items-02B27CCA.js │ │ │ -rw-r--r-- 0 root (0) root (0) 271 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.2/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 91760 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.2/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 100340 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.2/doc/html/public_key.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 100333 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.2/doc/html/public_key.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 207218 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.2/doc/html/public_key.html │ │ │ -rw-r--r-- 0 root (0) root (0) 10281 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.2/doc/html/public_key_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 70652 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.2/doc/html/public_key_records.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5965 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.2/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 131335 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/public_key-1.17.1.2/doc/html/using_public_key.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/ │ │ │ @@ -1555,24 +1555,24 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5820 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5612 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5552 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 161570 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/search_data-A1188F1F.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 161570 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/search_data-1DC1D27C.js │ │ │ -rw-r--r-- 0 root (0) root (0) 18284 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dist/sidebar_items-E5CACCF4.js │ │ │ -rw-r--r-- 0 root (0) root (0) 9774 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dtrace.html │ │ │ -rw-r--r-- 0 root (0) root (0) 47868 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/dyntrace.html │ │ │ -rw-r--r-- 0 root (0) root (0) 271 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 50868 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/instrument.html │ │ │ -rw-r--r-- 0 root (0) root (0) 72530 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/lttng.html │ │ │ -rw-r--r-- 0 root (0) root (0) 50090 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/msacc.html │ │ │ -rw-r--r-- 0 root (0) root (0) 78751 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 119346 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/runtime_tools.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 119342 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/runtime_tools.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 7584 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/runtime_tools_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 29115 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/scheduler.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5974 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12911 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/system_information.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9992 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/html/systemtap.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2.1/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2.1/doc/ │ │ │ @@ -1607,15 +1607,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 34692 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2.1/doc/html/error_logging.html │ │ │ -rw-r--r-- 0 root (0) root (0) 264 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2.1/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 70704 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2.1/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 42554 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2.1/doc/html/rb.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12226 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2.1/doc/html/rel.html │ │ │ -rw-r--r-- 0 root (0) root (0) 80666 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2.1/doc/html/release_handler.html │ │ │ -rw-r--r-- 0 root (0) root (0) 10822 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2.1/doc/html/relup.html │ │ │ --rw-r--r-- 0 root (0) root (0) 92583 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2.1/doc/html/sasl.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 92575 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2.1/doc/html/sasl.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 17199 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2.1/doc/html/sasl_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 7705 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2.1/doc/html/sasl_intro.html │ │ │ -rw-r--r-- 0 root (0) root (0) 17275 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2.1/doc/html/script.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5926 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2.1/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 40711 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2.1/doc/html/systools.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/ │ │ │ @@ -1654,15 +1654,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5552 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 549455 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/search_data-18B48D27.js │ │ │ -rw-r--r-- 0 root (0) root (0) 90029 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/sidebar_items-E4326166.js │ │ │ -rw-r--r-- 0 root (0) root (0) 263 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 61230 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5923 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 445369 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/snmp.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 445382 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/snmp.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 148375 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/snmp.html │ │ │ -rw-r--r-- 0 root (0) root (0) 39983 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/snmp_advanced_agent.html │ │ │ -rw-r--r-- 0 root (0) root (0) 62881 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/snmp_agent_config_files.html │ │ │ -rw-r--r-- 0 root (0) root (0) 51341 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/snmp_agent_funct_descr.html │ │ │ -rw-r--r-- 0 root (0) root (0) 18123 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/snmp_agent_netif.html │ │ │ -rw-r--r-- 0 root (0) root (0) 66609 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/snmp_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 8609 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/snmp_app_a.html │ │ │ @@ -1745,15 +1745,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 373816 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.7/doc/html/dist/search_data-B2C75D26.js │ │ │ -rw-r--r-- 0 root (0) root (0) 46168 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.7/doc/html/dist/sidebar_items-9B02B7D5.js │ │ │ -rw-r--r-- 0 root (0) root (0) 27804 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.7/doc/html/hardening.html │ │ │ -rw-r--r-- 0 root (0) root (0) 264 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.7/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 14206 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.7/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 235654 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.7/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5923 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.7/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 274576 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.7/doc/html/ssh.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 274569 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.7/doc/html/ssh.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 250458 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.7/doc/html/ssh.html │ │ │ -rw-r--r-- 0 root (0) root (0) 24859 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.7/doc/html/ssh_agent.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25764 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.7/doc/html/ssh_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 44362 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.7/doc/html/ssh_client_channel.html │ │ │ -rw-r--r-- 0 root (0) root (0) 23335 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.7/doc/html/ssh_client_key_api.html │ │ │ -rw-r--r-- 0 root (0) root (0) 78399 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.7/doc/html/ssh_connection.html │ │ │ -rw-r--r-- 0 root (0) root (0) 49063 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssh-5.2.11.7/doc/html/ssh_file.html │ │ │ @@ -1790,15 +1790,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5552 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.7/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.7/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 491166 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.7/doc/html/dist/search_data-66B4354E.js │ │ │ -rw-r--r-- 0 root (0) root (0) 27434 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.7/doc/html/dist/sidebar_items-4EB419B0.js │ │ │ -rw-r--r-- 0 root (0) root (0) 265 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.7/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 259463 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.7/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5926 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.7/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 213583 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.7/doc/html/ssl.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 213582 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.7/doc/html/ssl.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 322702 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.7/doc/html/ssl.html │ │ │ -rw-r--r-- 0 root (0) root (0) 17362 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.7/doc/html/ssl_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12890 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.7/doc/html/ssl_crl_cache.html │ │ │ -rw-r--r-- 0 root (0) root (0) 21815 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.7/doc/html/ssl_crl_cache_api.html │ │ │ -rw-r--r-- 0 root (0) root (0) 39391 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.7/doc/html/ssl_distribution.html │ │ │ -rw-r--r-- 0 root (0) root (0) 14213 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.7/doc/html/ssl_protocol.html │ │ │ -rw-r--r-- 0 root (0) root (0) 25887 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.7/doc/html/ssl_session_cache_api.html │ │ │ @@ -1896,15 +1896,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5938 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 47294 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/sets.html │ │ │ -rw-r--r-- 0 root (0) root (0) 106633 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/shell.html │ │ │ -rw-r--r-- 0 root (0) root (0) 10167 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/shell_default.html │ │ │ -rw-r--r-- 0 root (0) root (0) 49822 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/shell_docs.html │ │ │ -rw-r--r-- 0 root (0) root (0) 33204 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/slave.html │ │ │ -rw-r--r-- 0 root (0) root (0) 354831 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/sofs.html │ │ │ --rw-r--r-- 0 root (0) root (0) 1410896 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/stdlib.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 1410887 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/stdlib.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 15682 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/stdlib_app.html │ │ │ -rw-r--r-- 0 root (0) root (0) 192390 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/string.html │ │ │ -rw-r--r-- 0 root (0) root (0) 93086 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/supervisor.html │ │ │ -rw-r--r-- 0 root (0) root (0) 20623 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/supervisor_bridge.html │ │ │ -rw-r--r-- 0 root (0) root (0) 107262 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/sys.html │ │ │ -rw-r--r-- 0 root (0) root (0) 81772 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/timer.html │ │ │ -rw-r--r-- 0 root (0) root (0) 75028 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/unicode.html │ │ │ @@ -2033,15 +2033,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 263 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 67398 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/lcnt.html │ │ │ -rw-r--r-- 0 root (0) root (0) 53463 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/lcnt_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 18424 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/make.html │ │ │ -rw-r--r-- 0 root (0) root (0) 107061 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5926 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 28575 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/tags.html │ │ │ --rw-r--r-- 0 root (0) root (0) 239379 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/tools.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 239375 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/tools.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 174030 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/tprof.html │ │ │ -rw-r--r-- 0 root (0) root (0) 184557 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/xref.html │ │ │ -rw-r--r-- 0 root (0) root (0) 39639 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/xref_chapter.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3.1/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3.1/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3.1/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1610 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3.1/doc/html/.build.gz │ │ │ @@ -2071,15 +2071,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 1665163 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3.1/doc/html/dist/search_data-C8002B3E.js │ │ │ -rw-r--r-- 0 root (0) root (0) 578685 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3.1/doc/html/dist/sidebar_items-F7AE20D7.js │ │ │ -rw-r--r-- 0 root (0) root (0) 1720405 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3.1/doc/html/gl.html │ │ │ -rw-r--r-- 0 root (0) root (0) 77348 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3.1/doc/html/glu.html │ │ │ -rw-r--r-- 0 root (0) root (0) 262 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3.1/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 59333 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3.1/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5914 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3.1/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 1607661 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3.1/doc/html/wx.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 1607667 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3.1/doc/html/wx.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 54025 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3.1/doc/html/wx.html │ │ │ -rw-r--r-- 0 root (0) root (0) 19460 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3.1/doc/html/wxAcceleratorEntry.html │ │ │ -rw-r--r-- 0 root (0) root (0) 15138 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3.1/doc/html/wxAcceleratorTable.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12381 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3.1/doc/html/wxActivateEvent.html │ │ │ -rw-r--r-- 0 root (0) root (0) 19135 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3.1/doc/html/wxArtProvider.html │ │ │ -rw-r--r-- 0 root (0) root (0) 17647 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3.1/doc/html/wxAuiDockArt.html │ │ │ -rw-r--r-- 0 root (0) root (0) 63051 2026-05-05 12:33:17.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3.1/doc/html/wxAuiManager.html │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/Erlang System Documentation.epub │ │ │ ├── zipinfo {} │ │ │ │ @@ -1,93 +1,93 @@ │ │ │ │ -Zip file size: 661070 bytes, number of entries: 91 │ │ │ │ -?rw-r--r-- 6.1 unx 20 bx stor 26-May-05 13:52 mimetype │ │ │ │ -?rw-r--r-- 6.1 unx 17923 bx defN 26-May-05 13:52 OEBPS/versions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 4674 bx defN 26-May-05 13:52 OEBPS/upgrade.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 53440 bx defN 26-May-05 13:52 OEBPS/typespec.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2167 bx defN 26-May-05 13:52 OEBPS/tutorial.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 766 bx defN 26-May-05 13:52 OEBPS/title.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 46257 bx defN 26-May-05 13:52 OEBPS/tablesdatabases.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 12467 bx defN 26-May-05 13:52 OEBPS/system_principles.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 7347 bx defN 26-May-05 13:52 OEBPS/system_limits.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 63477 bx defN 26-May-05 13:52 OEBPS/sup_princ.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 254034 bx defN 26-May-05 13:52 OEBPS/statem.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 111725 bx defN 26-May-05 13:52 OEBPS/spec_proc.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 252382 bx defN 26-May-05 13:52 OEBPS/seq_prog.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 70944 bx defN 26-May-05 13:52 OEBPS/robustness.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 20878 bx defN 26-May-05 13:52 OEBPS/release_structure.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 61568 bx defN 26-May-05 13:52 OEBPS/release_handling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 4597 bx defN 26-May-05 13:52 OEBPS/reference_manual.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 19456 bx defN 26-May-05 13:52 OEBPS/ref_man_records.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 48277 bx defN 26-May-05 13:52 OEBPS/ref_man_processes.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 14478 bx defN 26-May-05 13:52 OEBPS/ref_man_functions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 49543 bx defN 26-May-05 13:52 OEBPS/records_macros.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2191 bx defN 26-May-05 13:52 OEBPS/readme.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 786 bx defN 26-May-05 13:52 OEBPS/programming_examples.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 40217 bx defN 26-May-05 13:52 OEBPS/prog_ex_records.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 15207 bx defN 26-May-05 13:52 OEBPS/profiling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 8502 bx defN 26-May-05 13:52 OEBPS/ports.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 3853 bx defN 26-May-05 13:52 OEBPS/patterns.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 13418 bx defN 26-May-05 13:52 OEBPS/overview.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 8960 bx defN 26-May-05 13:52 OEBPS/otp-patch-apply.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 9083 bx defN 26-May-05 13:52 OEBPS/opaques.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 22033 bx defN 26-May-05 13:52 OEBPS/nif.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 6368 bx defN 26-May-05 13:52 OEBPS/nav.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 25843 bx defN 26-May-05 13:52 OEBPS/modules.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 7013 bx defN 26-May-05 13:52 OEBPS/misc.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 5478 bx defN 26-May-05 13:52 OEBPS/memory.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 45533 bx defN 26-May-05 13:52 OEBPS/maps.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 39595 bx defN 26-May-05 13:52 OEBPS/macros.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 31440 bx defN 26-May-05 13:52 OEBPS/listhandling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 43066 bx defN 26-May-05 13:52 OEBPS/list_comprehensions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2212 bx defN 26-May-05 13:52 OEBPS/installation_guide.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 55528 bx defN 26-May-05 13:52 OEBPS/install.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 28230 bx defN 26-May-05 13:52 OEBPS/install-win32.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 35716 bx defN 26-May-05 13:52 OEBPS/install-cross.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 20859 bx defN 26-May-05 13:52 OEBPS/included_applications.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2354 bx defN 26-May-05 13:52 OEBPS/getting_started.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 31342 bx defN 26-May-05 13:52 OEBPS/gen_server_concepts.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 119136 bx defN 26-May-05 13:52 OEBPS/funs.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 8454 bx defN 26-May-05 13:52 OEBPS/features.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 256795 bx defN 26-May-05 13:52 OEBPS/expressions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 3572 bx defN 26-May-05 13:52 OEBPS/example.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 26771 bx defN 26-May-05 13:52 OEBPS/events.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 16653 bx defN 26-May-05 13:52 OEBPS/errors.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 13610 bx defN 26-May-05 13:52 OEBPS/error_logging.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 67909 bx defN 26-May-05 13:52 OEBPS/erl_interface.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 18221 bx defN 26-May-05 13:52 OEBPS/embedded.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2086 bx defN 26-May-05 13:52 OEBPS/efficiency_guide.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 46586 bx defN 26-May-05 13:52 OEBPS/eff_guide_processes.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 21210 bx defN 26-May-05 13:52 OEBPS/eff_guide_functions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 9339 bx defN 26-May-05 13:52 OEBPS/drivers.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 47181 bx defN 26-May-05 13:52 OEBPS/documentation.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 14893 bx defN 26-May-05 13:52 OEBPS/distributed_applications.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 24377 bx defN 26-May-05 13:52 OEBPS/distributed.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 14558 bx defN 26-May-05 13:52 OEBPS/dist/epub-erlang-QROVQRM3.css │ │ │ │ -?rw-r--r-- 6.1 unx 499 bx defN 26-May-05 13:52 OEBPS/dist/epub-LSJCIYTM.js │ │ │ │ -?rw-r--r-- 6.1 unx 36781 bx defN 26-May-05 13:52 OEBPS/design_principles.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 15004 bx defN 26-May-05 13:52 OEBPS/debugging.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 72899 bx defN 26-May-05 13:52 OEBPS/data_types.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 116636 bx defN 26-May-05 13:52 OEBPS/create_target.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 13188 bx defN 26-May-05 13:52 OEBPS/content.opf │ │ │ │ -?rw-r--r-- 6.1 unx 130016 bx defN 26-May-05 13:52 OEBPS/conc_prog.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 33232 bx defN 26-May-05 13:52 OEBPS/commoncaveats.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 11936 bx defN 26-May-05 13:52 OEBPS/code_loading.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 804 bx defN 26-May-05 13:52 OEBPS/cnode.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 5178 bx defN 26-May-05 13:52 OEBPS/character_set.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 51841 bx defN 26-May-05 13:52 OEBPS/c_portdriver.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 50666 bx defN 26-May-05 13:52 OEBPS/c_port.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 34820 bx defN 26-May-05 13:52 OEBPS/bit_syntax.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 53328 bx defN 26-May-05 13:52 OEBPS/binaryhandling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 7607 bx defN 26-May-05 13:52 OEBPS/benchmarking.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 5837 bx defN 26-May-05 13:52 OEBPS/assets/logo.png │ │ │ │ -?rw-r--r-- 6.1 unx 5837 bx defN 26-May-05 13:52 OEBPS/assets/erlang-logo.png │ │ │ │ -?rw-r--r-- 6.1 unx 7044 bx stor 26-May-05 13:52 OEBPS/assets/dist5.gif │ │ │ │ -?rw-r--r-- 6.1 unx 2939 bx stor 26-May-05 13:52 OEBPS/assets/dist4.gif │ │ │ │ -?rw-r--r-- 6.1 unx 5007 bx stor 26-May-05 13:52 OEBPS/assets/dist3.gif │ │ │ │ -?rw-r--r-- 6.1 unx 5214 bx stor 26-May-05 13:52 OEBPS/assets/dist2.gif │ │ │ │ -?rw-r--r-- 6.1 unx 2284 bx stor 26-May-05 13:52 OEBPS/assets/dist1.gif │ │ │ │ -?rw-r--r-- 6.1 unx 7982 bx stor 26-May-05 13:52 OEBPS/assets/ballpoint-pen.svg │ │ │ │ -?rw-r--r-- 6.1 unx 104219 bx defN 26-May-05 13:52 OEBPS/appup_cookbook.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 47930 bx defN 26-May-05 13:52 OEBPS/applications.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 252 bx defN 26-May-05 13:52 META-INF/container.xml │ │ │ │ -?rw-r--r-- 6.1 unx 162 bx defN 26-May-05 13:52 META-INF/com.apple.ibooks.display-options.xml │ │ │ │ -91 files, 3161740 bytes uncompressed, 645138 bytes compressed: 79.6% │ │ │ │ +Zip file size: 661082 bytes, number of entries: 91 │ │ │ │ +?rw-r--r-- 6.1 unx 20 bx stor 26-May-10 23:47 mimetype │ │ │ │ +?rw-r--r-- 6.1 unx 17923 bx defN 26-May-10 23:47 OEBPS/versions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 4674 bx defN 26-May-10 23:47 OEBPS/upgrade.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 53440 bx defN 26-May-10 23:47 OEBPS/typespec.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2167 bx defN 26-May-10 23:47 OEBPS/tutorial.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 766 bx defN 26-May-10 23:47 OEBPS/title.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 46257 bx defN 26-May-10 23:47 OEBPS/tablesdatabases.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 12467 bx defN 26-May-10 23:47 OEBPS/system_principles.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 7347 bx defN 26-May-10 23:47 OEBPS/system_limits.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 63477 bx defN 26-May-10 23:47 OEBPS/sup_princ.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 254034 bx defN 26-May-10 23:47 OEBPS/statem.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 111725 bx defN 26-May-10 23:47 OEBPS/spec_proc.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 252382 bx defN 26-May-10 23:47 OEBPS/seq_prog.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 70944 bx defN 26-May-10 23:47 OEBPS/robustness.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 20878 bx defN 26-May-10 23:47 OEBPS/release_structure.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 61568 bx defN 26-May-10 23:47 OEBPS/release_handling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 4597 bx defN 26-May-10 23:47 OEBPS/reference_manual.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 19456 bx defN 26-May-10 23:47 OEBPS/ref_man_records.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 48277 bx defN 26-May-10 23:47 OEBPS/ref_man_processes.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 14478 bx defN 26-May-10 23:47 OEBPS/ref_man_functions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 49543 bx defN 26-May-10 23:47 OEBPS/records_macros.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2191 bx defN 26-May-10 23:47 OEBPS/readme.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 786 bx defN 26-May-10 23:47 OEBPS/programming_examples.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 40217 bx defN 26-May-10 23:47 OEBPS/prog_ex_records.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 15207 bx defN 26-May-10 23:47 OEBPS/profiling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 8502 bx defN 26-May-10 23:47 OEBPS/ports.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 3853 bx defN 26-May-10 23:47 OEBPS/patterns.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 13418 bx defN 26-May-10 23:47 OEBPS/overview.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 8960 bx defN 26-May-10 23:47 OEBPS/otp-patch-apply.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 9083 bx defN 26-May-10 23:47 OEBPS/opaques.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 22033 bx defN 26-May-10 23:47 OEBPS/nif.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 6368 bx defN 26-May-10 23:47 OEBPS/nav.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 25843 bx defN 26-May-10 23:47 OEBPS/modules.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 7013 bx defN 26-May-10 23:47 OEBPS/misc.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 5478 bx defN 26-May-10 23:47 OEBPS/memory.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 45533 bx defN 26-May-10 23:47 OEBPS/maps.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 39595 bx defN 26-May-10 23:47 OEBPS/macros.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 31440 bx defN 26-May-10 23:47 OEBPS/listhandling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 43066 bx defN 26-May-10 23:47 OEBPS/list_comprehensions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2212 bx defN 26-May-10 23:47 OEBPS/installation_guide.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 55528 bx defN 26-May-10 23:47 OEBPS/install.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 28230 bx defN 26-May-10 23:47 OEBPS/install-win32.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 35716 bx defN 26-May-10 23:47 OEBPS/install-cross.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 20859 bx defN 26-May-10 23:47 OEBPS/included_applications.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2354 bx defN 26-May-10 23:47 OEBPS/getting_started.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 31342 bx defN 26-May-10 23:47 OEBPS/gen_server_concepts.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 119136 bx defN 26-May-10 23:47 OEBPS/funs.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 8454 bx defN 26-May-10 23:47 OEBPS/features.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 256795 bx defN 26-May-10 23:47 OEBPS/expressions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 3572 bx defN 26-May-10 23:47 OEBPS/example.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 26771 bx defN 26-May-10 23:47 OEBPS/events.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 16653 bx defN 26-May-10 23:47 OEBPS/errors.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 13610 bx defN 26-May-10 23:47 OEBPS/error_logging.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 67909 bx defN 26-May-10 23:47 OEBPS/erl_interface.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 18221 bx defN 26-May-10 23:47 OEBPS/embedded.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2086 bx defN 26-May-10 23:47 OEBPS/efficiency_guide.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 46586 bx defN 26-May-10 23:47 OEBPS/eff_guide_processes.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 21210 bx defN 26-May-10 23:47 OEBPS/eff_guide_functions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 9339 bx defN 26-May-10 23:47 OEBPS/drivers.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 47181 bx defN 26-May-10 23:47 OEBPS/documentation.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 14893 bx defN 26-May-10 23:47 OEBPS/distributed_applications.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 24377 bx defN 26-May-10 23:47 OEBPS/distributed.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 14558 bx defN 26-May-10 23:47 OEBPS/dist/epub-erlang-QROVQRM3.css │ │ │ │ +?rw-r--r-- 6.1 unx 499 bx defN 26-May-10 23:47 OEBPS/dist/epub-LSJCIYTM.js │ │ │ │ +?rw-r--r-- 6.1 unx 36781 bx defN 26-May-10 23:47 OEBPS/design_principles.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 15004 bx defN 26-May-10 23:47 OEBPS/debugging.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 72899 bx defN 26-May-10 23:47 OEBPS/data_types.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 116636 bx defN 26-May-10 23:47 OEBPS/create_target.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 13188 bx defN 26-May-10 23:47 OEBPS/content.opf │ │ │ │ +?rw-r--r-- 6.1 unx 130016 bx defN 26-May-10 23:47 OEBPS/conc_prog.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 33232 bx defN 26-May-10 23:47 OEBPS/commoncaveats.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 11936 bx defN 26-May-10 23:47 OEBPS/code_loading.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 804 bx defN 26-May-10 23:47 OEBPS/cnode.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 5178 bx defN 26-May-10 23:47 OEBPS/character_set.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 51841 bx defN 26-May-10 23:47 OEBPS/c_portdriver.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 50666 bx defN 26-May-10 23:47 OEBPS/c_port.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 34820 bx defN 26-May-10 23:47 OEBPS/bit_syntax.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 53328 bx defN 26-May-10 23:47 OEBPS/binaryhandling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 7607 bx defN 26-May-10 23:47 OEBPS/benchmarking.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 5837 bx defN 26-May-10 23:47 OEBPS/assets/logo.png │ │ │ │ +?rw-r--r-- 6.1 unx 5837 bx defN 26-May-10 23:47 OEBPS/assets/erlang-logo.png │ │ │ │ +?rw-r--r-- 6.1 unx 7044 bx stor 26-May-10 23:47 OEBPS/assets/dist5.gif │ │ │ │ +?rw-r--r-- 6.1 unx 2939 bx stor 26-May-10 23:47 OEBPS/assets/dist4.gif │ │ │ │ +?rw-r--r-- 6.1 unx 5007 bx stor 26-May-10 23:47 OEBPS/assets/dist3.gif │ │ │ │ +?rw-r--r-- 6.1 unx 5214 bx stor 26-May-10 23:47 OEBPS/assets/dist2.gif │ │ │ │ +?rw-r--r-- 6.1 unx 2284 bx stor 26-May-10 23:47 OEBPS/assets/dist1.gif │ │ │ │ +?rw-r--r-- 6.1 unx 7982 bx stor 26-May-10 23:47 OEBPS/assets/ballpoint-pen.svg │ │ │ │ +?rw-r--r-- 6.1 unx 104219 bx defN 26-May-10 23:47 OEBPS/appup_cookbook.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 47930 bx defN 26-May-10 23:47 OEBPS/applications.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 252 bx defN 26-May-10 23:47 META-INF/container.xml │ │ │ │ +?rw-r--r-- 6.1 unx 162 bx defN 26-May-10 23:47 META-INF/com.apple.ibooks.display-options.xml │ │ │ │ +91 files, 3161740 bytes uncompressed, 645150 bytes compressed: 79.6% │ │ │ ├── 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 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ +0000A Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 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 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -0002F Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ +0002B Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +0002F Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 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,30 +31,30 @@ │ │ │ │ │ │ │ │ 00056 LOCAL HEADER #2 04034B50 (67324752) │ │ │ │ 0005A Extract Zip Spec 14 (20) '2.0' │ │ │ │ 0005B Extract OS 00 (0) 'MS-DOS' │ │ │ │ 0005C General Purpose Flag 0000 (0) │ │ │ │ [Bits 1-2] 0 'Normal Compression' │ │ │ │ 0005E Compression Method 0008 (8) 'Deflated' │ │ │ │ -00060 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ +00060 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ 00064 CRC 7CFCE72A (2096949034) │ │ │ │ 00068 Compressed Size 000015AD (5549) │ │ │ │ 0006C Uncompressed Size 00004603 (17923) │ │ │ │ 00070 Filename Length 0014 (20) │ │ │ │ 00072 Extra Length 001C (28) │ │ │ │ 00074 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ # │ │ │ │ # WARNING: Offset 0x74: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ # Zero length filename │ │ │ │ # │ │ │ │ 00088 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ 0008A Length 0009 (9) │ │ │ │ 0008C Flags 03 (3) 'Modification Access' │ │ │ │ -0008D Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -00091 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ +0008D Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +00091 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ 00095 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ 00097 Length 000B (11) │ │ │ │ 00099 Version 01 (1) │ │ │ │ 0009A UID Size 04 (4) │ │ │ │ 0009B UID 00000000 (0) │ │ │ │ 0009F GID Size 04 (4) │ │ │ │ 000A0 GID 00000000 (0) │ │ │ │ @@ -62,30 +62,30 @@ │ │ │ │ │ │ │ │ 01651 LOCAL HEADER #3 04034B50 (67324752) │ │ │ │ 01655 Extract Zip Spec 14 (20) '2.0' │ │ │ │ 01656 Extract OS 00 (0) 'MS-DOS' │ │ │ │ 01657 General Purpose Flag 0000 (0) │ │ │ │ [Bits 1-2] 0 'Normal Compression' │ │ │ │ 01659 Compression Method 0008 (8) 'Deflated' │ │ │ │ -0165B Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ +0165B Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ 0165F CRC 741FDE60 (1948245600) │ │ │ │ 01663 Compressed Size 000006D6 (1750) │ │ │ │ 01667 Uncompressed Size 00001242 (4674) │ │ │ │ 0166B Filename Length 0013 (19) │ │ │ │ 0166D Extra Length 001C (28) │ │ │ │ 0166F Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ # │ │ │ │ # WARNING: Offset 0x166F: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ # Zero length filename │ │ │ │ # │ │ │ │ 01682 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ 01684 Length 0009 (9) │ │ │ │ 01686 Flags 03 (3) 'Modification Access' │ │ │ │ -01687 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -0168B Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ +01687 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +0168B Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ 0168F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ 01691 Length 000B (11) │ │ │ │ 01693 Version 01 (1) │ │ │ │ 01694 UID Size 04 (4) │ │ │ │ 01695 UID 00000000 (0) │ │ │ │ 01699 GID Size 04 (4) │ │ │ │ 0169A GID 00000000 (0) │ │ │ │ @@ -93,6187 +93,6187 @@ │ │ │ │ │ │ │ │ 01D74 LOCAL HEADER #4 04034B50 (67324752) │ │ │ │ 01D78 Extract Zip Spec 14 (20) '2.0' │ │ │ │ 01D79 Extract OS 00 (0) 'MS-DOS' │ │ │ │ 01D7A General Purpose Flag 0000 (0) │ │ │ │ [Bits 1-2] 0 'Normal Compression' │ │ │ │ 01D7C Compression Method 0008 (8) 'Deflated' │ │ │ │ -01D7E Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -01D82 CRC FBBFDC35 (4223654965) │ │ │ │ -01D86 Compressed Size 00002DA6 (11686) │ │ │ │ +01D7E Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +01D82 CRC 19F7DFEB (435675115) │ │ │ │ +01D86 Compressed Size 00002DA3 (11683) │ │ │ │ 01D8A Uncompressed Size 0000D0C0 (53440) │ │ │ │ 01D8E Filename Length 0014 (20) │ │ │ │ 01D90 Extra Length 001C (28) │ │ │ │ 01D92 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ # │ │ │ │ # WARNING: Offset 0x1D92: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ # Zero length filename │ │ │ │ # │ │ │ │ 01DA6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ 01DA8 Length 0009 (9) │ │ │ │ 01DAA Flags 03 (3) 'Modification Access' │ │ │ │ -01DAB Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -01DAF Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ +01DAB Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +01DAF Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ 01DB3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ 01DB5 Length 000B (11) │ │ │ │ 01DB7 Version 01 (1) │ │ │ │ 01DB8 UID Size 04 (4) │ │ │ │ 01DB9 UID 00000000 (0) │ │ │ │ 01DBD GID Size 04 (4) │ │ │ │ 01DBE GID 00000000 (0) │ │ │ │ 01DC2 PAYLOAD │ │ │ │ │ │ │ │ -04B68 LOCAL HEADER #5 04034B50 (67324752) │ │ │ │ -04B6C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -04B6D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -04B6E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -04B70 Compression Method 0008 (8) 'Deflated' │ │ │ │ -04B72 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -04B76 CRC AFEB34EA (2951427306) │ │ │ │ -04B7A Compressed Size 000003F0 (1008) │ │ │ │ -04B7E Uncompressed Size 00000877 (2167) │ │ │ │ -04B82 Filename Length 0014 (20) │ │ │ │ -04B84 Extra Length 001C (28) │ │ │ │ -04B86 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4B86: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -04B9A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -04B9C Length 0009 (9) │ │ │ │ -04B9E Flags 03 (3) 'Modification Access' │ │ │ │ -04B9F Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -04BA3 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -04BA7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -04BA9 Length 000B (11) │ │ │ │ -04BAB Version 01 (1) │ │ │ │ -04BAC UID Size 04 (4) │ │ │ │ -04BAD UID 00000000 (0) │ │ │ │ -04BB1 GID Size 04 (4) │ │ │ │ -04BB2 GID 00000000 (0) │ │ │ │ -04BB6 PAYLOAD │ │ │ │ - │ │ │ │ -04FA6 LOCAL HEADER #6 04034B50 (67324752) │ │ │ │ -04FAA Extract Zip Spec 14 (20) '2.0' │ │ │ │ -04FAB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -04FAC General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -04FAE Compression Method 0008 (8) 'Deflated' │ │ │ │ -04FB0 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -04FB4 CRC 21AF85A7 (565151143) │ │ │ │ -04FB8 Compressed Size 000001AE (430) │ │ │ │ -04FBC Uncompressed Size 000002FE (766) │ │ │ │ -04FC0 Filename Length 0011 (17) │ │ │ │ -04FC2 Extra Length 001C (28) │ │ │ │ -04FC4 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4FC4: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -04FD5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -04FD7 Length 0009 (9) │ │ │ │ -04FD9 Flags 03 (3) 'Modification Access' │ │ │ │ -04FDA Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -04FDE Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -04FE2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -04FE4 Length 000B (11) │ │ │ │ -04FE6 Version 01 (1) │ │ │ │ -04FE7 UID Size 04 (4) │ │ │ │ -04FE8 UID 00000000 (0) │ │ │ │ -04FEC GID Size 04 (4) │ │ │ │ -04FED GID 00000000 (0) │ │ │ │ -04FF1 PAYLOAD │ │ │ │ - │ │ │ │ -0519F LOCAL HEADER #7 04034B50 (67324752) │ │ │ │ -051A3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -051A4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -051A5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -051A7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -051A9 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -051AD CRC A0E52D3F (2699373887) │ │ │ │ -051B1 Compressed Size 000020CA (8394) │ │ │ │ -051B5 Uncompressed Size 0000B4B1 (46257) │ │ │ │ -051B9 Filename Length 001B (27) │ │ │ │ -051BB Extra Length 001C (28) │ │ │ │ -051BD Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x51BD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -051D8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -051DA Length 0009 (9) │ │ │ │ -051DC Flags 03 (3) 'Modification Access' │ │ │ │ -051DD Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -051E1 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -051E5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -051E7 Length 000B (11) │ │ │ │ -051E9 Version 01 (1) │ │ │ │ -051EA UID Size 04 (4) │ │ │ │ -051EB UID 00000000 (0) │ │ │ │ -051EF GID Size 04 (4) │ │ │ │ -051F0 GID 00000000 (0) │ │ │ │ -051F4 PAYLOAD │ │ │ │ - │ │ │ │ -072BE LOCAL HEADER #8 04034B50 (67324752) │ │ │ │ -072C2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -072C3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -072C4 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -072C6 Compression Method 0008 (8) 'Deflated' │ │ │ │ -072C8 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -072CC CRC 2D371F1E (758587166) │ │ │ │ -072D0 Compressed Size 00000E70 (3696) │ │ │ │ -072D4 Uncompressed Size 000030B3 (12467) │ │ │ │ -072D8 Filename Length 001D (29) │ │ │ │ -072DA Extra Length 001C (28) │ │ │ │ -072DC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x72DC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -072F9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -072FB Length 0009 (9) │ │ │ │ -072FD Flags 03 (3) 'Modification Access' │ │ │ │ -072FE Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -07302 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -07306 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -07308 Length 000B (11) │ │ │ │ -0730A Version 01 (1) │ │ │ │ -0730B UID Size 04 (4) │ │ │ │ -0730C UID 00000000 (0) │ │ │ │ -07310 GID Size 04 (4) │ │ │ │ -07311 GID 00000000 (0) │ │ │ │ -07315 PAYLOAD │ │ │ │ - │ │ │ │ -08185 LOCAL HEADER #9 04034B50 (67324752) │ │ │ │ -08189 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -0818A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -0818B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -0818D Compression Method 0008 (8) 'Deflated' │ │ │ │ -0818F Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -08193 CRC 7903D0CB (2030293195) │ │ │ │ -08197 Compressed Size 00000973 (2419) │ │ │ │ -0819B Uncompressed Size 00001CB3 (7347) │ │ │ │ -0819F Filename Length 0019 (25) │ │ │ │ -081A1 Extra Length 001C (28) │ │ │ │ -081A3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x81A3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -081BC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -081BE Length 0009 (9) │ │ │ │ -081C0 Flags 03 (3) 'Modification Access' │ │ │ │ -081C1 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -081C5 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -081C9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -081CB Length 000B (11) │ │ │ │ -081CD Version 01 (1) │ │ │ │ -081CE UID Size 04 (4) │ │ │ │ -081CF UID 00000000 (0) │ │ │ │ -081D3 GID Size 04 (4) │ │ │ │ -081D4 GID 00000000 (0) │ │ │ │ -081D8 PAYLOAD │ │ │ │ - │ │ │ │ -08B4B LOCAL HEADER #10 04034B50 (67324752) │ │ │ │ -08B4F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -08B50 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -08B51 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -08B53 Compression Method 0008 (8) 'Deflated' │ │ │ │ -08B55 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -08B59 CRC 9B28EE90 (2603150992) │ │ │ │ -08B5D Compressed Size 00003880 (14464) │ │ │ │ -08B61 Uncompressed Size 0000F7F5 (63477) │ │ │ │ -08B65 Filename Length 0015 (21) │ │ │ │ -08B67 Extra Length 001C (28) │ │ │ │ -08B69 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8B69: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -08B7E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -08B80 Length 0009 (9) │ │ │ │ -08B82 Flags 03 (3) 'Modification Access' │ │ │ │ -08B83 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -08B87 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -08B8B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -08B8D Length 000B (11) │ │ │ │ -08B8F Version 01 (1) │ │ │ │ -08B90 UID Size 04 (4) │ │ │ │ -08B91 UID 00000000 (0) │ │ │ │ -08B95 GID Size 04 (4) │ │ │ │ -08B96 GID 00000000 (0) │ │ │ │ -08B9A PAYLOAD │ │ │ │ - │ │ │ │ -0C41A LOCAL HEADER #11 04034B50 (67324752) │ │ │ │ -0C41E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -0C41F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -0C420 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -0C422 Compression Method 0008 (8) 'Deflated' │ │ │ │ -0C424 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -0C428 CRC A03CCBDB (2688338907) │ │ │ │ -0C42C Compressed Size 0000AB09 (43785) │ │ │ │ -0C430 Uncompressed Size 0003E052 (254034) │ │ │ │ -0C434 Filename Length 0012 (18) │ │ │ │ -0C436 Extra Length 001C (28) │ │ │ │ -0C438 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC438: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -0C44A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -0C44C Length 0009 (9) │ │ │ │ -0C44E Flags 03 (3) 'Modification Access' │ │ │ │ -0C44F Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -0C453 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -0C457 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -0C459 Length 000B (11) │ │ │ │ -0C45B Version 01 (1) │ │ │ │ -0C45C UID Size 04 (4) │ │ │ │ -0C45D UID 00000000 (0) │ │ │ │ -0C461 GID Size 04 (4) │ │ │ │ -0C462 GID 00000000 (0) │ │ │ │ -0C466 PAYLOAD │ │ │ │ - │ │ │ │ -16F6F LOCAL HEADER #12 04034B50 (67324752) │ │ │ │ -16F73 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -16F74 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -16F75 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -16F77 Compression Method 0008 (8) 'Deflated' │ │ │ │ -16F79 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -16F7D CRC 4650ADC7 (1179692487) │ │ │ │ -16F81 Compressed Size 00003B0B (15115) │ │ │ │ -16F85 Uncompressed Size 0001B46D (111725) │ │ │ │ -16F89 Filename Length 0015 (21) │ │ │ │ -16F8B Extra Length 001C (28) │ │ │ │ -16F8D Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x16F8D: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -16FA2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -16FA4 Length 0009 (9) │ │ │ │ -16FA6 Flags 03 (3) 'Modification Access' │ │ │ │ -16FA7 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -16FAB Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -16FAF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -16FB1 Length 000B (11) │ │ │ │ -16FB3 Version 01 (1) │ │ │ │ -16FB4 UID Size 04 (4) │ │ │ │ -16FB5 UID 00000000 (0) │ │ │ │ -16FB9 GID Size 04 (4) │ │ │ │ -16FBA GID 00000000 (0) │ │ │ │ -16FBE PAYLOAD │ │ │ │ - │ │ │ │ -1AAC9 LOCAL HEADER #13 04034B50 (67324752) │ │ │ │ -1AACD Extract Zip Spec 14 (20) '2.0' │ │ │ │ -1AACE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -1AACF General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -1AAD1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -1AAD3 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -1AAD7 CRC A2B5FF2C (2729836332) │ │ │ │ -1AADB Compressed Size 000091C4 (37316) │ │ │ │ -1AADF Uncompressed Size 0003D9DE (252382) │ │ │ │ -1AAE3 Filename Length 0014 (20) │ │ │ │ -1AAE5 Extra Length 001C (28) │ │ │ │ -1AAE7 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x1AAE7: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -1AAFB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -1AAFD Length 0009 (9) │ │ │ │ -1AAFF Flags 03 (3) 'Modification Access' │ │ │ │ -1AB00 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -1AB04 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -1AB08 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -1AB0A Length 000B (11) │ │ │ │ -1AB0C Version 01 (1) │ │ │ │ -1AB0D UID Size 04 (4) │ │ │ │ -1AB0E UID 00000000 (0) │ │ │ │ -1AB12 GID Size 04 (4) │ │ │ │ -1AB13 GID 00000000 (0) │ │ │ │ -1AB17 PAYLOAD │ │ │ │ - │ │ │ │ -23CDB LOCAL HEADER #14 04034B50 (67324752) │ │ │ │ -23CDF Extract Zip Spec 14 (20) '2.0' │ │ │ │ -23CE0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -23CE1 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -23CE3 Compression Method 0008 (8) 'Deflated' │ │ │ │ -23CE5 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -23CE9 CRC 49680290 (1231553168) │ │ │ │ -23CED Compressed Size 00002A65 (10853) │ │ │ │ -23CF1 Uncompressed Size 00011520 (70944) │ │ │ │ -23CF5 Filename Length 0016 (22) │ │ │ │ -23CF7 Extra Length 001C (28) │ │ │ │ -23CF9 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x23CF9: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -23D0F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -23D11 Length 0009 (9) │ │ │ │ -23D13 Flags 03 (3) 'Modification Access' │ │ │ │ -23D14 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -23D18 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -23D1C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -23D1E Length 000B (11) │ │ │ │ -23D20 Version 01 (1) │ │ │ │ -23D21 UID Size 04 (4) │ │ │ │ -23D22 UID 00000000 (0) │ │ │ │ -23D26 GID Size 04 (4) │ │ │ │ -23D27 GID 00000000 (0) │ │ │ │ -23D2B PAYLOAD │ │ │ │ - │ │ │ │ -26790 LOCAL HEADER #15 04034B50 (67324752) │ │ │ │ -26794 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -26795 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -26796 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -26798 Compression Method 0008 (8) 'Deflated' │ │ │ │ -2679A Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -2679E CRC 29BEF9C0 (700381632) │ │ │ │ -267A2 Compressed Size 000014DB (5339) │ │ │ │ -267A6 Uncompressed Size 0000518E (20878) │ │ │ │ -267AA Filename Length 001D (29) │ │ │ │ -267AC Extra Length 001C (28) │ │ │ │ -267AE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x267AE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -267CB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -267CD Length 0009 (9) │ │ │ │ -267CF Flags 03 (3) 'Modification Access' │ │ │ │ -267D0 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -267D4 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -267D8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -267DA Length 000B (11) │ │ │ │ -267DC Version 01 (1) │ │ │ │ -267DD UID Size 04 (4) │ │ │ │ -267DE UID 00000000 (0) │ │ │ │ -267E2 GID Size 04 (4) │ │ │ │ -267E3 GID 00000000 (0) │ │ │ │ -267E7 PAYLOAD │ │ │ │ - │ │ │ │ -27CC2 LOCAL HEADER #16 04034B50 (67324752) │ │ │ │ -27CC6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -27CC7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -27CC8 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -27CCA Compression Method 0008 (8) 'Deflated' │ │ │ │ -27CCC Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -27CD0 CRC B011A27F (2953945727) │ │ │ │ -27CD4 Compressed Size 000039AF (14767) │ │ │ │ -27CD8 Uncompressed Size 0000F080 (61568) │ │ │ │ -27CDC Filename Length 001C (28) │ │ │ │ -27CDE Extra Length 001C (28) │ │ │ │ -27CE0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x27CE0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -27CFC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -27CFE Length 0009 (9) │ │ │ │ -27D00 Flags 03 (3) 'Modification Access' │ │ │ │ -27D01 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -27D05 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -27D09 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -27D0B Length 000B (11) │ │ │ │ -27D0D Version 01 (1) │ │ │ │ -27D0E UID Size 04 (4) │ │ │ │ -27D0F UID 00000000 (0) │ │ │ │ -27D13 GID Size 04 (4) │ │ │ │ -27D14 GID 00000000 (0) │ │ │ │ -27D18 PAYLOAD │ │ │ │ - │ │ │ │ -2B6C7 LOCAL HEADER #17 04034B50 (67324752) │ │ │ │ -2B6CB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -2B6CC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -2B6CD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -2B6CF Compression Method 0008 (8) 'Deflated' │ │ │ │ -2B6D1 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -2B6D5 CRC 7A88ED58 (2055794008) │ │ │ │ -2B6D9 Compressed Size 000006A3 (1699) │ │ │ │ -2B6DD Uncompressed Size 000011F5 (4597) │ │ │ │ -2B6E1 Filename Length 001C (28) │ │ │ │ -2B6E3 Extra Length 001C (28) │ │ │ │ -2B6E5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x2B6E5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -2B701 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -2B703 Length 0009 (9) │ │ │ │ -2B705 Flags 03 (3) 'Modification Access' │ │ │ │ -2B706 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -2B70A Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -2B70E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -2B710 Length 000B (11) │ │ │ │ -2B712 Version 01 (1) │ │ │ │ -2B713 UID Size 04 (4) │ │ │ │ -2B714 UID 00000000 (0) │ │ │ │ -2B718 GID Size 04 (4) │ │ │ │ -2B719 GID 00000000 (0) │ │ │ │ -2B71D PAYLOAD │ │ │ │ - │ │ │ │ -2BDC0 LOCAL HEADER #18 04034B50 (67324752) │ │ │ │ -2BDC4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -2BDC5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -2BDC6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -2BDC8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -2BDCA Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -2BDCE CRC 0FC65721 (264656673) │ │ │ │ -2BDD2 Compressed Size 00001081 (4225) │ │ │ │ -2BDD6 Uncompressed Size 00004C00 (19456) │ │ │ │ -2BDDA Filename Length 001B (27) │ │ │ │ -2BDDC Extra Length 001C (28) │ │ │ │ -2BDDE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x2BDDE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -2BDF9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -2BDFB Length 0009 (9) │ │ │ │ -2BDFD Flags 03 (3) 'Modification Access' │ │ │ │ -2BDFE Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -2BE02 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -2BE06 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -2BE08 Length 000B (11) │ │ │ │ -2BE0A Version 01 (1) │ │ │ │ -2BE0B UID Size 04 (4) │ │ │ │ -2BE0C UID 00000000 (0) │ │ │ │ -2BE10 GID Size 04 (4) │ │ │ │ -2BE11 GID 00000000 (0) │ │ │ │ -2BE15 PAYLOAD │ │ │ │ - │ │ │ │ -2CE96 LOCAL HEADER #19 04034B50 (67324752) │ │ │ │ -2CE9A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -2CE9B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -2CE9C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -2CE9E Compression Method 0008 (8) 'Deflated' │ │ │ │ -2CEA0 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -2CEA4 CRC 75FAACB3 (1979362483) │ │ │ │ -2CEA8 Compressed Size 000033AB (13227) │ │ │ │ -2CEAC Uncompressed Size 0000BC95 (48277) │ │ │ │ -2CEB0 Filename Length 001D (29) │ │ │ │ -2CEB2 Extra Length 001C (28) │ │ │ │ -2CEB4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x2CEB4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -2CED1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -2CED3 Length 0009 (9) │ │ │ │ -2CED5 Flags 03 (3) 'Modification Access' │ │ │ │ -2CED6 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -2CEDA Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -2CEDE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -2CEE0 Length 000B (11) │ │ │ │ -2CEE2 Version 01 (1) │ │ │ │ -2CEE3 UID Size 04 (4) │ │ │ │ -2CEE4 UID 00000000 (0) │ │ │ │ -2CEE8 GID Size 04 (4) │ │ │ │ -2CEE9 GID 00000000 (0) │ │ │ │ -2CEED PAYLOAD │ │ │ │ - │ │ │ │ -30298 LOCAL HEADER #20 04034B50 (67324752) │ │ │ │ -3029C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3029D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3029E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -302A0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -302A2 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -302A6 CRC D83D1D51 (3627883857) │ │ │ │ -302AA Compressed Size 00000D6B (3435) │ │ │ │ -302AE Uncompressed Size 0000388E (14478) │ │ │ │ -302B2 Filename Length 001D (29) │ │ │ │ -302B4 Extra Length 001C (28) │ │ │ │ -302B6 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x302B6: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -302D3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -302D5 Length 0009 (9) │ │ │ │ -302D7 Flags 03 (3) 'Modification Access' │ │ │ │ -302D8 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -302DC Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -302E0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -302E2 Length 000B (11) │ │ │ │ -302E4 Version 01 (1) │ │ │ │ -302E5 UID Size 04 (4) │ │ │ │ -302E6 UID 00000000 (0) │ │ │ │ -302EA GID Size 04 (4) │ │ │ │ -302EB GID 00000000 (0) │ │ │ │ -302EF PAYLOAD │ │ │ │ - │ │ │ │ -3105A LOCAL HEADER #21 04034B50 (67324752) │ │ │ │ -3105E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3105F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -31060 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -31062 Compression Method 0008 (8) 'Deflated' │ │ │ │ -31064 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -31068 CRC C751E37B (3344032635) │ │ │ │ -3106C Compressed Size 00001C69 (7273) │ │ │ │ -31070 Uncompressed Size 0000C187 (49543) │ │ │ │ -31074 Filename Length 001A (26) │ │ │ │ -31076 Extra Length 001C (28) │ │ │ │ -31078 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x31078: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -31092 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -31094 Length 0009 (9) │ │ │ │ -31096 Flags 03 (3) 'Modification Access' │ │ │ │ -31097 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -3109B Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -3109F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -310A1 Length 000B (11) │ │ │ │ -310A3 Version 01 (1) │ │ │ │ -310A4 UID Size 04 (4) │ │ │ │ -310A5 UID 00000000 (0) │ │ │ │ -310A9 GID Size 04 (4) │ │ │ │ -310AA GID 00000000 (0) │ │ │ │ -310AE PAYLOAD │ │ │ │ - │ │ │ │ -32D17 LOCAL HEADER #22 04034B50 (67324752) │ │ │ │ -32D1B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -32D1C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -32D1D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -32D1F Compression Method 0008 (8) 'Deflated' │ │ │ │ -32D21 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -32D25 CRC C41B95CB (3290142155) │ │ │ │ -32D29 Compressed Size 000003A4 (932) │ │ │ │ -32D2D Uncompressed Size 0000088F (2191) │ │ │ │ -32D31 Filename Length 0012 (18) │ │ │ │ -32D33 Extra Length 001C (28) │ │ │ │ -32D35 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x32D35: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -32D47 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -32D49 Length 0009 (9) │ │ │ │ -32D4B Flags 03 (3) 'Modification Access' │ │ │ │ -32D4C Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -32D50 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -32D54 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -32D56 Length 000B (11) │ │ │ │ -32D58 Version 01 (1) │ │ │ │ -32D59 UID Size 04 (4) │ │ │ │ -32D5A UID 00000000 (0) │ │ │ │ -32D5E GID Size 04 (4) │ │ │ │ -32D5F GID 00000000 (0) │ │ │ │ -32D63 PAYLOAD │ │ │ │ - │ │ │ │ -33107 LOCAL HEADER #23 04034B50 (67324752) │ │ │ │ -3310B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3310C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3310D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3310F Compression Method 0008 (8) 'Deflated' │ │ │ │ -33111 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -33115 CRC 40B68220 (1085702688) │ │ │ │ -33119 Compressed Size 000001D4 (468) │ │ │ │ -3311D Uncompressed Size 00000312 (786) │ │ │ │ -33121 Filename Length 0020 (32) │ │ │ │ -33123 Extra Length 001C (28) │ │ │ │ -33125 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x33125: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -33145 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -33147 Length 0009 (9) │ │ │ │ -33149 Flags 03 (3) 'Modification Access' │ │ │ │ -3314A Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -3314E Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -33152 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -33154 Length 000B (11) │ │ │ │ -33156 Version 01 (1) │ │ │ │ -33157 UID Size 04 (4) │ │ │ │ -33158 UID 00000000 (0) │ │ │ │ -3315C GID Size 04 (4) │ │ │ │ -3315D GID 00000000 (0) │ │ │ │ -33161 PAYLOAD │ │ │ │ - │ │ │ │ -33335 LOCAL HEADER #24 04034B50 (67324752) │ │ │ │ -33339 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3333A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3333B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3333D Compression Method 0008 (8) 'Deflated' │ │ │ │ -3333F Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -33343 CRC AEDAC289 (2933572233) │ │ │ │ -33347 Compressed Size 000017AA (6058) │ │ │ │ -3334B Uncompressed Size 00009D19 (40217) │ │ │ │ -3334F Filename Length 001B (27) │ │ │ │ -33351 Extra Length 001C (28) │ │ │ │ -33353 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x33353: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3336E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -33370 Length 0009 (9) │ │ │ │ -33372 Flags 03 (3) 'Modification Access' │ │ │ │ -33373 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -33377 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -3337B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3337D Length 000B (11) │ │ │ │ -3337F Version 01 (1) │ │ │ │ -33380 UID Size 04 (4) │ │ │ │ -33381 UID 00000000 (0) │ │ │ │ -33385 GID Size 04 (4) │ │ │ │ -33386 GID 00000000 (0) │ │ │ │ -3338A PAYLOAD │ │ │ │ - │ │ │ │ -34B34 LOCAL HEADER #25 04034B50 (67324752) │ │ │ │ -34B38 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -34B39 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -34B3A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -34B3C Compression Method 0008 (8) 'Deflated' │ │ │ │ -34B3E Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -34B42 CRC 8B976C32 (2341956658) │ │ │ │ -34B46 Compressed Size 00001374 (4980) │ │ │ │ -34B4A Uncompressed Size 00003B67 (15207) │ │ │ │ -34B4E Filename Length 0015 (21) │ │ │ │ -34B50 Extra Length 001C (28) │ │ │ │ -34B52 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x34B52: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -34B67 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -34B69 Length 0009 (9) │ │ │ │ -34B6B Flags 03 (3) 'Modification Access' │ │ │ │ -34B6C Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -34B70 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -34B74 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -34B76 Length 000B (11) │ │ │ │ -34B78 Version 01 (1) │ │ │ │ -34B79 UID Size 04 (4) │ │ │ │ -34B7A UID 00000000 (0) │ │ │ │ -34B7E GID Size 04 (4) │ │ │ │ -34B7F GID 00000000 (0) │ │ │ │ -34B83 PAYLOAD │ │ │ │ - │ │ │ │ -35EF7 LOCAL HEADER #26 04034B50 (67324752) │ │ │ │ -35EFB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -35EFC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -35EFD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -35EFF Compression Method 0008 (8) 'Deflated' │ │ │ │ -35F01 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -35F05 CRC 2ACCA91C (718055708) │ │ │ │ -35F09 Compressed Size 00000AD3 (2771) │ │ │ │ -35F0D Uncompressed Size 00002136 (8502) │ │ │ │ -35F11 Filename Length 0011 (17) │ │ │ │ -35F13 Extra Length 001C (28) │ │ │ │ -35F15 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x35F15: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -35F26 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -35F28 Length 0009 (9) │ │ │ │ -35F2A Flags 03 (3) 'Modification Access' │ │ │ │ -35F2B Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -35F2F Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -35F33 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -35F35 Length 000B (11) │ │ │ │ -35F37 Version 01 (1) │ │ │ │ -35F38 UID Size 04 (4) │ │ │ │ -35F39 UID 00000000 (0) │ │ │ │ -35F3D GID Size 04 (4) │ │ │ │ -35F3E GID 00000000 (0) │ │ │ │ -35F42 PAYLOAD │ │ │ │ - │ │ │ │ -36A15 LOCAL HEADER #27 04034B50 (67324752) │ │ │ │ -36A19 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -36A1A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -36A1B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -36A1D Compression Method 0008 (8) 'Deflated' │ │ │ │ -36A1F Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -36A23 CRC 984552B7 (2554679991) │ │ │ │ -36A27 Compressed Size 000003FE (1022) │ │ │ │ -36A2B Uncompressed Size 00000F0D (3853) │ │ │ │ -36A2F Filename Length 0014 (20) │ │ │ │ -36A31 Extra Length 001C (28) │ │ │ │ -36A33 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x36A33: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -36A47 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -36A49 Length 0009 (9) │ │ │ │ -36A4B Flags 03 (3) 'Modification Access' │ │ │ │ -36A4C Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -36A50 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -36A54 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -36A56 Length 000B (11) │ │ │ │ -36A58 Version 01 (1) │ │ │ │ -36A59 UID Size 04 (4) │ │ │ │ -36A5A UID 00000000 (0) │ │ │ │ -36A5E GID Size 04 (4) │ │ │ │ -36A5F GID 00000000 (0) │ │ │ │ -36A63 PAYLOAD │ │ │ │ - │ │ │ │ -36E61 LOCAL HEADER #28 04034B50 (67324752) │ │ │ │ -36E65 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -36E66 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -36E67 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -36E69 Compression Method 0008 (8) 'Deflated' │ │ │ │ -36E6B Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -36E6F CRC F2ED2F43 (4075630403) │ │ │ │ -36E73 Compressed Size 00001263 (4707) │ │ │ │ -36E77 Uncompressed Size 0000346A (13418) │ │ │ │ -36E7B Filename Length 0014 (20) │ │ │ │ -36E7D Extra Length 001C (28) │ │ │ │ -36E7F Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x36E7F: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -36E93 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -36E95 Length 0009 (9) │ │ │ │ -36E97 Flags 03 (3) 'Modification Access' │ │ │ │ -36E98 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -36E9C Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -36EA0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -36EA2 Length 000B (11) │ │ │ │ -36EA4 Version 01 (1) │ │ │ │ -36EA5 UID Size 04 (4) │ │ │ │ -36EA6 UID 00000000 (0) │ │ │ │ -36EAA GID Size 04 (4) │ │ │ │ -36EAB GID 00000000 (0) │ │ │ │ -36EAF PAYLOAD │ │ │ │ - │ │ │ │ -38112 LOCAL HEADER #29 04034B50 (67324752) │ │ │ │ -38116 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -38117 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -38118 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3811A Compression Method 0008 (8) 'Deflated' │ │ │ │ -3811C Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -38120 CRC 3233182B (842209323) │ │ │ │ -38124 Compressed Size 00000AD1 (2769) │ │ │ │ -38128 Uncompressed Size 00002300 (8960) │ │ │ │ -3812C Filename Length 001B (27) │ │ │ │ -3812E Extra Length 001C (28) │ │ │ │ -38130 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x38130: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3814B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3814D Length 0009 (9) │ │ │ │ -3814F Flags 03 (3) 'Modification Access' │ │ │ │ -38150 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -38154 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -38158 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3815A Length 000B (11) │ │ │ │ -3815C Version 01 (1) │ │ │ │ -3815D UID Size 04 (4) │ │ │ │ -3815E UID 00000000 (0) │ │ │ │ -38162 GID Size 04 (4) │ │ │ │ -38163 GID 00000000 (0) │ │ │ │ -38167 PAYLOAD │ │ │ │ - │ │ │ │ -38C38 LOCAL HEADER #30 04034B50 (67324752) │ │ │ │ -38C3C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -38C3D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -38C3E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -38C40 Compression Method 0008 (8) 'Deflated' │ │ │ │ -38C42 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -38C46 CRC 0BD59D77 (198548855) │ │ │ │ -38C4A Compressed Size 00000A8E (2702) │ │ │ │ -38C4E Uncompressed Size 0000237B (9083) │ │ │ │ -38C52 Filename Length 0013 (19) │ │ │ │ -38C54 Extra Length 001C (28) │ │ │ │ -38C56 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x38C56: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -38C69 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -38C6B Length 0009 (9) │ │ │ │ -38C6D Flags 03 (3) 'Modification Access' │ │ │ │ -38C6E Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -38C72 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -38C76 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -38C78 Length 000B (11) │ │ │ │ -38C7A Version 01 (1) │ │ │ │ -38C7B UID Size 04 (4) │ │ │ │ -38C7C UID 00000000 (0) │ │ │ │ -38C80 GID Size 04 (4) │ │ │ │ -38C81 GID 00000000 (0) │ │ │ │ -38C85 PAYLOAD │ │ │ │ - │ │ │ │ -39713 LOCAL HEADER #31 04034B50 (67324752) │ │ │ │ -39717 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -39718 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -39719 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3971B Compression Method 0008 (8) 'Deflated' │ │ │ │ -3971D Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -39721 CRC 4DABAB86 (1303096198) │ │ │ │ -39725 Compressed Size 000010E5 (4325) │ │ │ │ -39729 Uncompressed Size 00005611 (22033) │ │ │ │ -3972D Filename Length 000F (15) │ │ │ │ -3972F Extra Length 001C (28) │ │ │ │ -39731 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x39731: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -39740 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -39742 Length 0009 (9) │ │ │ │ -39744 Flags 03 (3) 'Modification Access' │ │ │ │ -39745 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -39749 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -3974D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3974F Length 000B (11) │ │ │ │ -39751 Version 01 (1) │ │ │ │ -39752 UID Size 04 (4) │ │ │ │ -39753 UID 00000000 (0) │ │ │ │ -39757 GID Size 04 (4) │ │ │ │ -39758 GID 00000000 (0) │ │ │ │ -3975C PAYLOAD │ │ │ │ - │ │ │ │ -3A841 LOCAL HEADER #32 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 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -3A84F CRC C70CE575 (3339511157) │ │ │ │ -3A853 Compressed Size 0000066B (1643) │ │ │ │ -3A857 Uncompressed Size 000018E0 (6368) │ │ │ │ -3A85B Filename Length 000F (15) │ │ │ │ -3A85D Extra Length 001C (28) │ │ │ │ -3A85F Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3A85F: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3A86E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3A870 Length 0009 (9) │ │ │ │ -3A872 Flags 03 (3) 'Modification Access' │ │ │ │ -3A873 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -3A877 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -3A87B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3A87D Length 000B (11) │ │ │ │ -3A87F Version 01 (1) │ │ │ │ -3A880 UID Size 04 (4) │ │ │ │ -3A881 UID 00000000 (0) │ │ │ │ -3A885 GID Size 04 (4) │ │ │ │ -3A886 GID 00000000 (0) │ │ │ │ -3A88A PAYLOAD │ │ │ │ - │ │ │ │ -3AEF5 LOCAL HEADER #33 04034B50 (67324752) │ │ │ │ -3AEF9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3AEFA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3AEFB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3AEFD Compression Method 0008 (8) 'Deflated' │ │ │ │ -3AEFF Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -3AF03 CRC B33717CF (3006732239) │ │ │ │ -3AF07 Compressed Size 00001A4B (6731) │ │ │ │ -3AF0B Uncompressed Size 000064F3 (25843) │ │ │ │ -3AF0F Filename Length 0013 (19) │ │ │ │ -3AF11 Extra Length 001C (28) │ │ │ │ -3AF13 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3AF13: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3AF26 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3AF28 Length 0009 (9) │ │ │ │ -3AF2A Flags 03 (3) 'Modification Access' │ │ │ │ -3AF2B Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -3AF2F Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -3AF33 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3AF35 Length 000B (11) │ │ │ │ -3AF37 Version 01 (1) │ │ │ │ -3AF38 UID Size 04 (4) │ │ │ │ -3AF39 UID 00000000 (0) │ │ │ │ -3AF3D GID Size 04 (4) │ │ │ │ -3AF3E GID 00000000 (0) │ │ │ │ -3AF42 PAYLOAD │ │ │ │ - │ │ │ │ -3C98D LOCAL HEADER #34 04034B50 (67324752) │ │ │ │ -3C991 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3C992 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3C993 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3C995 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3C997 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -3C99B CRC BA8C2020 (3129745440) │ │ │ │ -3C99F Compressed Size 000009A7 (2471) │ │ │ │ -3C9A3 Uncompressed Size 00001B65 (7013) │ │ │ │ -3C9A7 Filename Length 0010 (16) │ │ │ │ -3C9A9 Extra Length 001C (28) │ │ │ │ -3C9AB Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3C9AB: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3C9BB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3C9BD Length 0009 (9) │ │ │ │ -3C9BF Flags 03 (3) 'Modification Access' │ │ │ │ -3C9C0 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -3C9C4 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -3C9C8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3C9CA Length 000B (11) │ │ │ │ -3C9CC Version 01 (1) │ │ │ │ -3C9CD UID Size 04 (4) │ │ │ │ -3C9CE UID 00000000 (0) │ │ │ │ -3C9D2 GID Size 04 (4) │ │ │ │ -3C9D3 GID 00000000 (0) │ │ │ │ -3C9D7 PAYLOAD │ │ │ │ - │ │ │ │ -3D37E LOCAL HEADER #35 04034B50 (67324752) │ │ │ │ -3D382 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3D383 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3D384 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3D386 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3D388 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -3D38C CRC 61869F50 (1636212560) │ │ │ │ -3D390 Compressed Size 000006B9 (1721) │ │ │ │ -3D394 Uncompressed Size 00001566 (5478) │ │ │ │ -3D398 Filename Length 0012 (18) │ │ │ │ -3D39A Extra Length 001C (28) │ │ │ │ -3D39C Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3D39C: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3D3AE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3D3B0 Length 0009 (9) │ │ │ │ -3D3B2 Flags 03 (3) 'Modification Access' │ │ │ │ -3D3B3 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -3D3B7 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -3D3BB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3D3BD Length 000B (11) │ │ │ │ -3D3BF Version 01 (1) │ │ │ │ -3D3C0 UID Size 04 (4) │ │ │ │ -3D3C1 UID 00000000 (0) │ │ │ │ -3D3C5 GID Size 04 (4) │ │ │ │ -3D3C6 GID 00000000 (0) │ │ │ │ -3D3CA PAYLOAD │ │ │ │ - │ │ │ │ -3DA83 LOCAL HEADER #36 04034B50 (67324752) │ │ │ │ -3DA87 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3DA88 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3DA89 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3DA8B Compression Method 0008 (8) 'Deflated' │ │ │ │ -3DA8D Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -3DA91 CRC 16F2754B (384988491) │ │ │ │ -3DA95 Compressed Size 00002A14 (10772) │ │ │ │ -3DA99 Uncompressed Size 0000B1DD (45533) │ │ │ │ -3DA9D Filename Length 0010 (16) │ │ │ │ -3DA9F Extra Length 001C (28) │ │ │ │ -3DAA1 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3DAA1: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3DAB1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3DAB3 Length 0009 (9) │ │ │ │ -3DAB5 Flags 03 (3) 'Modification Access' │ │ │ │ -3DAB6 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -3DABA Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -3DABE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3DAC0 Length 000B (11) │ │ │ │ -3DAC2 Version 01 (1) │ │ │ │ -3DAC3 UID Size 04 (4) │ │ │ │ -3DAC4 UID 00000000 (0) │ │ │ │ -3DAC8 GID Size 04 (4) │ │ │ │ -3DAC9 GID 00000000 (0) │ │ │ │ -3DACD PAYLOAD │ │ │ │ - │ │ │ │ -404E1 LOCAL HEADER #37 04034B50 (67324752) │ │ │ │ -404E5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -404E6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -404E7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -404E9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -404EB Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -404EF CRC 23389B09 (590912265) │ │ │ │ -404F3 Compressed Size 00001E89 (7817) │ │ │ │ -404F7 Uncompressed Size 00009AAB (39595) │ │ │ │ -404FB Filename Length 0012 (18) │ │ │ │ -404FD Extra Length 001C (28) │ │ │ │ -404FF Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x404FF: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -40511 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -40513 Length 0009 (9) │ │ │ │ -40515 Flags 03 (3) 'Modification Access' │ │ │ │ -40516 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -4051A Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -4051E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -40520 Length 000B (11) │ │ │ │ -40522 Version 01 (1) │ │ │ │ -40523 UID Size 04 (4) │ │ │ │ -40524 UID 00000000 (0) │ │ │ │ -40528 GID Size 04 (4) │ │ │ │ -40529 GID 00000000 (0) │ │ │ │ -4052D PAYLOAD │ │ │ │ - │ │ │ │ -423B6 LOCAL HEADER #38 04034B50 (67324752) │ │ │ │ -423BA Extract Zip Spec 14 (20) '2.0' │ │ │ │ -423BB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -423BC General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -423BE Compression Method 0008 (8) 'Deflated' │ │ │ │ -423C0 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -423C4 CRC 13847F5A (327450458) │ │ │ │ -423C8 Compressed Size 0000147C (5244) │ │ │ │ -423CC Uncompressed Size 00007AD0 (31440) │ │ │ │ -423D0 Filename Length 0018 (24) │ │ │ │ -423D2 Extra Length 001C (28) │ │ │ │ -423D4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x423D4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -423EC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -423EE Length 0009 (9) │ │ │ │ -423F0 Flags 03 (3) 'Modification Access' │ │ │ │ -423F1 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -423F5 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -423F9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -423FB Length 000B (11) │ │ │ │ -423FD Version 01 (1) │ │ │ │ -423FE UID Size 04 (4) │ │ │ │ -423FF UID 00000000 (0) │ │ │ │ -42403 GID Size 04 (4) │ │ │ │ -42404 GID 00000000 (0) │ │ │ │ -42408 PAYLOAD │ │ │ │ - │ │ │ │ -43884 LOCAL HEADER #39 04034B50 (67324752) │ │ │ │ -43888 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -43889 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4388A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4388C Compression Method 0008 (8) 'Deflated' │ │ │ │ -4388E Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -43892 CRC 021B6F33 (35352371) │ │ │ │ -43896 Compressed Size 000018DA (6362) │ │ │ │ -4389A Uncompressed Size 0000A83A (43066) │ │ │ │ -4389E Filename Length 001F (31) │ │ │ │ -438A0 Extra Length 001C (28) │ │ │ │ -438A2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x438A2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -438C1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -438C3 Length 0009 (9) │ │ │ │ -438C5 Flags 03 (3) 'Modification Access' │ │ │ │ -438C6 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -438CA Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -438CE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -438D0 Length 000B (11) │ │ │ │ -438D2 Version 01 (1) │ │ │ │ -438D3 UID Size 04 (4) │ │ │ │ -438D4 UID 00000000 (0) │ │ │ │ -438D8 GID Size 04 (4) │ │ │ │ -438D9 GID 00000000 (0) │ │ │ │ -438DD PAYLOAD │ │ │ │ - │ │ │ │ -451B7 LOCAL HEADER #40 04034B50 (67324752) │ │ │ │ -451BB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -451BC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -451BD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -451BF Compression Method 0008 (8) 'Deflated' │ │ │ │ -451C1 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -451C5 CRC 8E4EBF9E (2387525534) │ │ │ │ -451C9 Compressed Size 000003F8 (1016) │ │ │ │ -451CD Uncompressed Size 000008A4 (2212) │ │ │ │ -451D1 Filename Length 001E (30) │ │ │ │ -451D3 Extra Length 001C (28) │ │ │ │ -451D5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x451D5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -451F3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -451F5 Length 0009 (9) │ │ │ │ -451F7 Flags 03 (3) 'Modification Access' │ │ │ │ -451F8 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -451FC Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -45200 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -45202 Length 000B (11) │ │ │ │ -45204 Version 01 (1) │ │ │ │ -45205 UID Size 04 (4) │ │ │ │ -45206 UID 00000000 (0) │ │ │ │ -4520A GID Size 04 (4) │ │ │ │ -4520B GID 00000000 (0) │ │ │ │ -4520F PAYLOAD │ │ │ │ - │ │ │ │ -45607 LOCAL HEADER #41 04034B50 (67324752) │ │ │ │ -4560B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4560C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4560D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4560F Compression Method 0008 (8) 'Deflated' │ │ │ │ -45611 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -45615 CRC D9FD2FF3 (3657248755) │ │ │ │ -45619 Compressed Size 00004296 (17046) │ │ │ │ -4561D Uncompressed Size 0000D8E8 (55528) │ │ │ │ -45621 Filename Length 0013 (19) │ │ │ │ -45623 Extra Length 001C (28) │ │ │ │ -45625 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x45625: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -45638 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -4563A Length 0009 (9) │ │ │ │ -4563C Flags 03 (3) 'Modification Access' │ │ │ │ -4563D Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -45641 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -45645 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -45647 Length 000B (11) │ │ │ │ -45649 Version 01 (1) │ │ │ │ -4564A UID Size 04 (4) │ │ │ │ -4564B UID 00000000 (0) │ │ │ │ -4564F GID Size 04 (4) │ │ │ │ -45650 GID 00000000 (0) │ │ │ │ -45654 PAYLOAD │ │ │ │ - │ │ │ │ -498EA LOCAL HEADER #42 04034B50 (67324752) │ │ │ │ -498EE Extract Zip Spec 14 (20) '2.0' │ │ │ │ -498EF Extract OS 00 (0) 'MS-DOS' │ │ │ │ -498F0 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -498F2 Compression Method 0008 (8) 'Deflated' │ │ │ │ -498F4 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -498F8 CRC A60BB6F3 (2785785587) │ │ │ │ -498FC Compressed Size 000026C4 (9924) │ │ │ │ -49900 Uncompressed Size 00006E46 (28230) │ │ │ │ -49904 Filename Length 0019 (25) │ │ │ │ -49906 Extra Length 001C (28) │ │ │ │ -49908 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x49908: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -49921 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -49923 Length 0009 (9) │ │ │ │ -49925 Flags 03 (3) 'Modification Access' │ │ │ │ -49926 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -4992A Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -4992E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -49930 Length 000B (11) │ │ │ │ -49932 Version 01 (1) │ │ │ │ -49933 UID Size 04 (4) │ │ │ │ -49934 UID 00000000 (0) │ │ │ │ -49938 GID Size 04 (4) │ │ │ │ -49939 GID 00000000 (0) │ │ │ │ -4993D PAYLOAD │ │ │ │ - │ │ │ │ -4C001 LOCAL HEADER #43 04034B50 (67324752) │ │ │ │ -4C005 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4C006 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4C007 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4C009 Compression Method 0008 (8) 'Deflated' │ │ │ │ -4C00B Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -4C00F CRC A84F7DF4 (2823781876) │ │ │ │ -4C013 Compressed Size 0000273B (10043) │ │ │ │ -4C017 Uncompressed Size 00008B84 (35716) │ │ │ │ -4C01B Filename Length 0019 (25) │ │ │ │ -4C01D Extra Length 001C (28) │ │ │ │ -4C01F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4C01F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -4C038 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -4C03A Length 0009 (9) │ │ │ │ -4C03C Flags 03 (3) 'Modification Access' │ │ │ │ -4C03D Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -4C041 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -4C045 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -4C047 Length 000B (11) │ │ │ │ -4C049 Version 01 (1) │ │ │ │ -4C04A UID Size 04 (4) │ │ │ │ -4C04B UID 00000000 (0) │ │ │ │ -4C04F GID Size 04 (4) │ │ │ │ -4C050 GID 00000000 (0) │ │ │ │ -4C054 PAYLOAD │ │ │ │ - │ │ │ │ -4E78F LOCAL HEADER #44 04034B50 (67324752) │ │ │ │ -4E793 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4E794 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4E795 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4E797 Compression Method 0008 (8) 'Deflated' │ │ │ │ -4E799 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -4E79D CRC 20889E86 (545824390) │ │ │ │ -4E7A1 Compressed Size 00000CF1 (3313) │ │ │ │ -4E7A5 Uncompressed Size 0000517B (20859) │ │ │ │ -4E7A9 Filename Length 0021 (33) │ │ │ │ -4E7AB Extra Length 001C (28) │ │ │ │ -4E7AD Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4E7AD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -4E7CE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -4E7D0 Length 0009 (9) │ │ │ │ -4E7D2 Flags 03 (3) 'Modification Access' │ │ │ │ -4E7D3 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -4E7D7 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -4E7DB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -4E7DD Length 000B (11) │ │ │ │ -4E7DF Version 01 (1) │ │ │ │ -4E7E0 UID Size 04 (4) │ │ │ │ -4E7E1 UID 00000000 (0) │ │ │ │ -4E7E5 GID Size 04 (4) │ │ │ │ -4E7E6 GID 00000000 (0) │ │ │ │ -4E7EA PAYLOAD │ │ │ │ - │ │ │ │ -4F4DB LOCAL HEADER #45 04034B50 (67324752) │ │ │ │ -4F4DF Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4F4E0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4F4E1 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4F4E3 Compression Method 0008 (8) 'Deflated' │ │ │ │ -4F4E5 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -4F4E9 CRC 4527F9F0 (1160247792) │ │ │ │ -4F4ED Compressed Size 00000469 (1129) │ │ │ │ -4F4F1 Uncompressed Size 00000932 (2354) │ │ │ │ -4F4F5 Filename Length 001B (27) │ │ │ │ -4F4F7 Extra Length 001C (28) │ │ │ │ -4F4F9 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4F4F9: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -4F514 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -4F516 Length 0009 (9) │ │ │ │ -4F518 Flags 03 (3) 'Modification Access' │ │ │ │ -4F519 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -4F51D Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -4F521 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -4F523 Length 000B (11) │ │ │ │ -4F525 Version 01 (1) │ │ │ │ -4F526 UID Size 04 (4) │ │ │ │ -4F527 UID 00000000 (0) │ │ │ │ -4F52B GID Size 04 (4) │ │ │ │ -4F52C GID 00000000 (0) │ │ │ │ -4F530 PAYLOAD │ │ │ │ - │ │ │ │ -4F999 LOCAL HEADER #46 04034B50 (67324752) │ │ │ │ -4F99D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4F99E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4F99F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4F9A1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -4F9A3 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -4F9A7 CRC 51FBB037 (1375449143) │ │ │ │ -4F9AB Compressed Size 000016F1 (5873) │ │ │ │ -4F9AF Uncompressed Size 00007A6E (31342) │ │ │ │ -4F9B3 Filename Length 001F (31) │ │ │ │ -4F9B5 Extra Length 001C (28) │ │ │ │ -4F9B7 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4F9B7: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -4F9D6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -4F9D8 Length 0009 (9) │ │ │ │ -4F9DA Flags 03 (3) 'Modification Access' │ │ │ │ -4F9DB Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -4F9DF Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -4F9E3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -4F9E5 Length 000B (11) │ │ │ │ -4F9E7 Version 01 (1) │ │ │ │ -4F9E8 UID Size 04 (4) │ │ │ │ -4F9E9 UID 00000000 (0) │ │ │ │ -4F9ED GID Size 04 (4) │ │ │ │ -4F9EE GID 00000000 (0) │ │ │ │ -4F9F2 PAYLOAD │ │ │ │ - │ │ │ │ -510E3 LOCAL HEADER #47 04034B50 (67324752) │ │ │ │ -510E7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -510E8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -510E9 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -510EB Compression Method 0008 (8) 'Deflated' │ │ │ │ -510ED Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -510F1 CRC 2414B628 (605337128) │ │ │ │ -510F5 Compressed Size 00004161 (16737) │ │ │ │ -510F9 Uncompressed Size 0001D160 (119136) │ │ │ │ -510FD Filename Length 0010 (16) │ │ │ │ -510FF Extra Length 001C (28) │ │ │ │ -51101 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x51101: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -51111 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -51113 Length 0009 (9) │ │ │ │ -51115 Flags 03 (3) 'Modification Access' │ │ │ │ -51116 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -5111A Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -5111E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -51120 Length 000B (11) │ │ │ │ -51122 Version 01 (1) │ │ │ │ -51123 UID Size 04 (4) │ │ │ │ -51124 UID 00000000 (0) │ │ │ │ -51128 GID Size 04 (4) │ │ │ │ -51129 GID 00000000 (0) │ │ │ │ -5112D PAYLOAD │ │ │ │ - │ │ │ │ -5528E LOCAL HEADER #48 04034B50 (67324752) │ │ │ │ -55292 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -55293 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -55294 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -55296 Compression Method 0008 (8) 'Deflated' │ │ │ │ -55298 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -5529C CRC 1C486A07 (474507783) │ │ │ │ -552A0 Compressed Size 00000A98 (2712) │ │ │ │ -552A4 Uncompressed Size 00002106 (8454) │ │ │ │ -552A8 Filename Length 0014 (20) │ │ │ │ -552AA Extra Length 001C (28) │ │ │ │ -552AC Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x552AC: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -552C0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -552C2 Length 0009 (9) │ │ │ │ -552C4 Flags 03 (3) 'Modification Access' │ │ │ │ -552C5 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -552C9 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -552CD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -552CF Length 000B (11) │ │ │ │ -552D1 Version 01 (1) │ │ │ │ -552D2 UID Size 04 (4) │ │ │ │ -552D3 UID 00000000 (0) │ │ │ │ -552D7 GID Size 04 (4) │ │ │ │ -552D8 GID 00000000 (0) │ │ │ │ -552DC PAYLOAD │ │ │ │ - │ │ │ │ -55D74 LOCAL HEADER #49 04034B50 (67324752) │ │ │ │ -55D78 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -55D79 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -55D7A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -55D7C Compression Method 0008 (8) 'Deflated' │ │ │ │ -55D7E Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -55D82 CRC 7F22DF82 (2132991874) │ │ │ │ -55D86 Compressed Size 0000AD78 (44408) │ │ │ │ -55D8A Uncompressed Size 0003EB1B (256795) │ │ │ │ -55D8E Filename Length 0017 (23) │ │ │ │ -55D90 Extra Length 001C (28) │ │ │ │ -55D92 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x55D92: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -55DA9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -55DAB Length 0009 (9) │ │ │ │ -55DAD Flags 03 (3) 'Modification Access' │ │ │ │ -55DAE Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -55DB2 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -55DB6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -55DB8 Length 000B (11) │ │ │ │ -55DBA Version 01 (1) │ │ │ │ -55DBB UID Size 04 (4) │ │ │ │ -55DBC UID 00000000 (0) │ │ │ │ -55DC0 GID Size 04 (4) │ │ │ │ -55DC1 GID 00000000 (0) │ │ │ │ -55DC5 PAYLOAD │ │ │ │ - │ │ │ │ -60B3D LOCAL HEADER #50 04034B50 (67324752) │ │ │ │ -60B41 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -60B42 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -60B43 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -60B45 Compression Method 0008 (8) 'Deflated' │ │ │ │ -60B47 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -60B4B CRC A0A15D62 (2694929762) │ │ │ │ -60B4F Compressed Size 00000464 (1124) │ │ │ │ -60B53 Uncompressed Size 00000DF4 (3572) │ │ │ │ -60B57 Filename Length 0013 (19) │ │ │ │ -60B59 Extra Length 001C (28) │ │ │ │ -60B5B Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x60B5B: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -60B6E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -60B70 Length 0009 (9) │ │ │ │ -60B72 Flags 03 (3) 'Modification Access' │ │ │ │ -60B73 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -60B77 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -60B7B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -60B7D Length 000B (11) │ │ │ │ -60B7F Version 01 (1) │ │ │ │ -60B80 UID Size 04 (4) │ │ │ │ -60B81 UID 00000000 (0) │ │ │ │ -60B85 GID Size 04 (4) │ │ │ │ -60B86 GID 00000000 (0) │ │ │ │ -60B8A PAYLOAD │ │ │ │ - │ │ │ │ -60FEE LOCAL HEADER #51 04034B50 (67324752) │ │ │ │ -60FF2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -60FF3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -60FF4 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -60FF6 Compression Method 0008 (8) 'Deflated' │ │ │ │ -60FF8 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -60FFC CRC D825044E (3626304590) │ │ │ │ -61000 Compressed Size 000014D6 (5334) │ │ │ │ -61004 Uncompressed Size 00006893 (26771) │ │ │ │ -61008 Filename Length 0012 (18) │ │ │ │ -6100A Extra Length 001C (28) │ │ │ │ -6100C Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6100C: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6101E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -61020 Length 0009 (9) │ │ │ │ -61022 Flags 03 (3) 'Modification Access' │ │ │ │ -61023 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -61027 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -6102B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6102D Length 000B (11) │ │ │ │ -6102F Version 01 (1) │ │ │ │ -61030 UID Size 04 (4) │ │ │ │ -61031 UID 00000000 (0) │ │ │ │ -61035 GID Size 04 (4) │ │ │ │ -61036 GID 00000000 (0) │ │ │ │ -6103A PAYLOAD │ │ │ │ - │ │ │ │ -62510 LOCAL HEADER #52 04034B50 (67324752) │ │ │ │ -62514 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -62515 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -62516 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -62518 Compression Method 0008 (8) 'Deflated' │ │ │ │ -6251A Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -6251E CRC C193E548 (3247695176) │ │ │ │ -62522 Compressed Size 000011F0 (4592) │ │ │ │ -62526 Uncompressed Size 0000410D (16653) │ │ │ │ -6252A Filename Length 0012 (18) │ │ │ │ -6252C Extra Length 001C (28) │ │ │ │ -6252E Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6252E: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -62540 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -62542 Length 0009 (9) │ │ │ │ -62544 Flags 03 (3) 'Modification Access' │ │ │ │ -62545 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -62549 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -6254D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6254F Length 000B (11) │ │ │ │ -62551 Version 01 (1) │ │ │ │ -62552 UID Size 04 (4) │ │ │ │ -62553 UID 00000000 (0) │ │ │ │ -62557 GID Size 04 (4) │ │ │ │ -62558 GID 00000000 (0) │ │ │ │ -6255C PAYLOAD │ │ │ │ - │ │ │ │ -6374C LOCAL HEADER #53 04034B50 (67324752) │ │ │ │ -63750 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -63751 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -63752 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -63754 Compression Method 0008 (8) 'Deflated' │ │ │ │ -63756 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -6375A CRC 10C49B8F (281320335) │ │ │ │ -6375E Compressed Size 000009D9 (2521) │ │ │ │ -63762 Uncompressed Size 0000352A (13610) │ │ │ │ -63766 Filename Length 0019 (25) │ │ │ │ -63768 Extra Length 001C (28) │ │ │ │ -6376A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6376A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -63783 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -63785 Length 0009 (9) │ │ │ │ -63787 Flags 03 (3) 'Modification Access' │ │ │ │ -63788 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -6378C Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -63790 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -63792 Length 000B (11) │ │ │ │ -63794 Version 01 (1) │ │ │ │ -63795 UID Size 04 (4) │ │ │ │ -63796 UID 00000000 (0) │ │ │ │ -6379A GID Size 04 (4) │ │ │ │ -6379B GID 00000000 (0) │ │ │ │ -6379F PAYLOAD │ │ │ │ - │ │ │ │ -64178 LOCAL HEADER #54 04034B50 (67324752) │ │ │ │ -6417C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6417D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6417E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -64180 Compression Method 0008 (8) 'Deflated' │ │ │ │ -64182 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -64186 CRC F2CB28F8 (4073400568) │ │ │ │ -6418A Compressed Size 00002023 (8227) │ │ │ │ -6418E Uncompressed Size 00010945 (67909) │ │ │ │ -64192 Filename Length 0019 (25) │ │ │ │ -64194 Extra Length 001C (28) │ │ │ │ -64196 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x64196: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -641AF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -641B1 Length 0009 (9) │ │ │ │ -641B3 Flags 03 (3) 'Modification Access' │ │ │ │ -641B4 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -641B8 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -641BC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -641BE Length 000B (11) │ │ │ │ -641C0 Version 01 (1) │ │ │ │ -641C1 UID Size 04 (4) │ │ │ │ -641C2 UID 00000000 (0) │ │ │ │ -641C6 GID Size 04 (4) │ │ │ │ -641C7 GID 00000000 (0) │ │ │ │ -641CB PAYLOAD │ │ │ │ - │ │ │ │ -661EE LOCAL HEADER #55 04034B50 (67324752) │ │ │ │ -661F2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -661F3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -661F4 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -661F6 Compression Method 0008 (8) 'Deflated' │ │ │ │ -661F8 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -661FC CRC D0372DB1 (3493277105) │ │ │ │ -66200 Compressed Size 0000177F (6015) │ │ │ │ -66204 Uncompressed Size 0000472D (18221) │ │ │ │ -66208 Filename Length 0014 (20) │ │ │ │ -6620A Extra Length 001C (28) │ │ │ │ -6620C Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6620C: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -66220 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -66222 Length 0009 (9) │ │ │ │ -66224 Flags 03 (3) 'Modification Access' │ │ │ │ -66225 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -66229 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -6622D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6622F Length 000B (11) │ │ │ │ -66231 Version 01 (1) │ │ │ │ -66232 UID Size 04 (4) │ │ │ │ -66233 UID 00000000 (0) │ │ │ │ -66237 GID Size 04 (4) │ │ │ │ -66238 GID 00000000 (0) │ │ │ │ -6623C PAYLOAD │ │ │ │ - │ │ │ │ -679BB LOCAL HEADER #56 04034B50 (67324752) │ │ │ │ -679BF Extract Zip Spec 14 (20) '2.0' │ │ │ │ -679C0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -679C1 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -679C3 Compression Method 0008 (8) 'Deflated' │ │ │ │ -679C5 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -679C9 CRC FAE30038 (4209180728) │ │ │ │ -679CD Compressed Size 0000040B (1035) │ │ │ │ -679D1 Uncompressed Size 00000826 (2086) │ │ │ │ -679D5 Filename Length 001C (28) │ │ │ │ -679D7 Extra Length 001C (28) │ │ │ │ -679D9 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x679D9: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -679F5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -679F7 Length 0009 (9) │ │ │ │ -679F9 Flags 03 (3) 'Modification Access' │ │ │ │ -679FA Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -679FE Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -67A02 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -67A04 Length 000B (11) │ │ │ │ -67A06 Version 01 (1) │ │ │ │ -67A07 UID Size 04 (4) │ │ │ │ -67A08 UID 00000000 (0) │ │ │ │ -67A0C GID Size 04 (4) │ │ │ │ -67A0D GID 00000000 (0) │ │ │ │ -67A11 PAYLOAD │ │ │ │ - │ │ │ │ -67E1C LOCAL HEADER #57 04034B50 (67324752) │ │ │ │ -67E20 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -67E21 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -67E22 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -67E24 Compression Method 0008 (8) 'Deflated' │ │ │ │ -67E26 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -67E2A CRC 60701273 (1617957491) │ │ │ │ -67E2E Compressed Size 00002499 (9369) │ │ │ │ -67E32 Uncompressed Size 0000B5FA (46586) │ │ │ │ -67E36 Filename Length 001F (31) │ │ │ │ -67E38 Extra Length 001C (28) │ │ │ │ -67E3A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x67E3A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -67E59 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -67E5B Length 0009 (9) │ │ │ │ -67E5D Flags 03 (3) 'Modification Access' │ │ │ │ -67E5E Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -67E62 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -67E66 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -67E68 Length 000B (11) │ │ │ │ -67E6A Version 01 (1) │ │ │ │ -67E6B UID Size 04 (4) │ │ │ │ -67E6C UID 00000000 (0) │ │ │ │ -67E70 GID Size 04 (4) │ │ │ │ -67E71 GID 00000000 (0) │ │ │ │ -67E75 PAYLOAD │ │ │ │ - │ │ │ │ -6A30E LOCAL HEADER #58 04034B50 (67324752) │ │ │ │ -6A312 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6A313 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6A314 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6A316 Compression Method 0008 (8) 'Deflated' │ │ │ │ -6A318 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -6A31C CRC 92A69325 (2460390181) │ │ │ │ -6A320 Compressed Size 00000E81 (3713) │ │ │ │ -6A324 Uncompressed Size 000052DA (21210) │ │ │ │ -6A328 Filename Length 001F (31) │ │ │ │ -6A32A Extra Length 001C (28) │ │ │ │ -6A32C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6A32C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6A34B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6A34D Length 0009 (9) │ │ │ │ -6A34F Flags 03 (3) 'Modification Access' │ │ │ │ -6A350 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -6A354 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -6A358 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6A35A Length 000B (11) │ │ │ │ -6A35C Version 01 (1) │ │ │ │ -6A35D UID Size 04 (4) │ │ │ │ -6A35E UID 00000000 (0) │ │ │ │ -6A362 GID Size 04 (4) │ │ │ │ -6A363 GID 00000000 (0) │ │ │ │ -6A367 PAYLOAD │ │ │ │ - │ │ │ │ -6B1E8 LOCAL HEADER #59 04034B50 (67324752) │ │ │ │ -6B1EC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6B1ED Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6B1EE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6B1F0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -6B1F2 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -6B1F6 CRC A77B8D6E (2809892206) │ │ │ │ -6B1FA Compressed Size 00000A46 (2630) │ │ │ │ -6B1FE Uncompressed Size 0000247B (9339) │ │ │ │ -6B202 Filename Length 0013 (19) │ │ │ │ -6B204 Extra Length 001C (28) │ │ │ │ -6B206 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6B206: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6B219 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6B21B Length 0009 (9) │ │ │ │ -6B21D Flags 03 (3) 'Modification Access' │ │ │ │ -6B21E Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -6B222 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -6B226 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6B228 Length 000B (11) │ │ │ │ -6B22A Version 01 (1) │ │ │ │ -6B22B UID Size 04 (4) │ │ │ │ -6B22C UID 00000000 (0) │ │ │ │ -6B230 GID Size 04 (4) │ │ │ │ -6B231 GID 00000000 (0) │ │ │ │ -6B235 PAYLOAD │ │ │ │ - │ │ │ │ -6BC7B LOCAL HEADER #60 04034B50 (67324752) │ │ │ │ -6BC7F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6BC80 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6BC81 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6BC83 Compression Method 0008 (8) 'Deflated' │ │ │ │ -6BC85 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -6BC89 CRC 30B97F5F (817463135) │ │ │ │ -6BC8D Compressed Size 00002486 (9350) │ │ │ │ -6BC91 Uncompressed Size 0000B84D (47181) │ │ │ │ -6BC95 Filename Length 0019 (25) │ │ │ │ -6BC97 Extra Length 001C (28) │ │ │ │ -6BC99 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6BC99: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6BCB2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6BCB4 Length 0009 (9) │ │ │ │ -6BCB6 Flags 03 (3) 'Modification Access' │ │ │ │ -6BCB7 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -6BCBB Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -6BCBF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6BCC1 Length 000B (11) │ │ │ │ -6BCC3 Version 01 (1) │ │ │ │ -6BCC4 UID Size 04 (4) │ │ │ │ -6BCC5 UID 00000000 (0) │ │ │ │ -6BCC9 GID Size 04 (4) │ │ │ │ -6BCCA GID 00000000 (0) │ │ │ │ -6BCCE PAYLOAD │ │ │ │ - │ │ │ │ -6E154 LOCAL HEADER #61 04034B50 (67324752) │ │ │ │ -6E158 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6E159 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6E15A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6E15C Compression Method 0008 (8) 'Deflated' │ │ │ │ -6E15E Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -6E162 CRC DC97F08B (3700945035) │ │ │ │ -6E166 Compressed Size 00000EFB (3835) │ │ │ │ -6E16A Uncompressed Size 00003A2D (14893) │ │ │ │ -6E16E Filename Length 0024 (36) │ │ │ │ -6E170 Extra Length 001C (28) │ │ │ │ -6E172 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6E172: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6E196 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6E198 Length 0009 (9) │ │ │ │ -6E19A Flags 03 (3) 'Modification Access' │ │ │ │ -6E19B Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -6E19F Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -6E1A3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6E1A5 Length 000B (11) │ │ │ │ -6E1A7 Version 01 (1) │ │ │ │ -6E1A8 UID Size 04 (4) │ │ │ │ -6E1A9 UID 00000000 (0) │ │ │ │ -6E1AD GID Size 04 (4) │ │ │ │ -6E1AE GID 00000000 (0) │ │ │ │ -6E1B2 PAYLOAD │ │ │ │ - │ │ │ │ -6F0AD LOCAL HEADER #62 04034B50 (67324752) │ │ │ │ -6F0B1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6F0B2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6F0B3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6F0B5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -6F0B7 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -6F0BB CRC 396B41A9 (963330473) │ │ │ │ -6F0BF Compressed Size 00001ABA (6842) │ │ │ │ -6F0C3 Uncompressed Size 00005F39 (24377) │ │ │ │ -6F0C7 Filename Length 0017 (23) │ │ │ │ -6F0C9 Extra Length 001C (28) │ │ │ │ -6F0CB Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6F0CB: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6F0E2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6F0E4 Length 0009 (9) │ │ │ │ -6F0E6 Flags 03 (3) 'Modification Access' │ │ │ │ -6F0E7 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -6F0EB Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -6F0EF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6F0F1 Length 000B (11) │ │ │ │ -6F0F3 Version 01 (1) │ │ │ │ -6F0F4 UID Size 04 (4) │ │ │ │ -6F0F5 UID 00000000 (0) │ │ │ │ -6F0F9 GID Size 04 (4) │ │ │ │ -6F0FA GID 00000000 (0) │ │ │ │ -6F0FE PAYLOAD │ │ │ │ - │ │ │ │ -70BB8 LOCAL HEADER #63 04034B50 (67324752) │ │ │ │ -70BBC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -70BBD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -70BBE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -70BC0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -70BC2 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -70BC6 CRC 3DD8544C (1037587532) │ │ │ │ -70BCA Compressed Size 00000ED1 (3793) │ │ │ │ -70BCE Uncompressed Size 000038DE (14558) │ │ │ │ -70BD2 Filename Length 0023 (35) │ │ │ │ -70BD4 Extra Length 001C (28) │ │ │ │ -70BD6 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x70BD6: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -70BF9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -70BFB Length 0009 (9) │ │ │ │ -70BFD Flags 03 (3) 'Modification Access' │ │ │ │ -70BFE Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -70C02 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -70C06 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -70C08 Length 000B (11) │ │ │ │ -70C0A Version 01 (1) │ │ │ │ -70C0B UID Size 04 (4) │ │ │ │ -70C0C UID 00000000 (0) │ │ │ │ -70C10 GID Size 04 (4) │ │ │ │ -70C11 GID 00000000 (0) │ │ │ │ -70C15 PAYLOAD │ │ │ │ - │ │ │ │ -71AE6 LOCAL HEADER #64 04034B50 (67324752) │ │ │ │ -71AEA Extract Zip Spec 14 (20) '2.0' │ │ │ │ -71AEB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -71AEC General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -71AEE Compression Method 0008 (8) 'Deflated' │ │ │ │ -71AF0 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -71AF4 CRC 2DB7929F (767005343) │ │ │ │ -71AF8 Compressed Size 00000113 (275) │ │ │ │ -71AFC Uncompressed Size 000001F3 (499) │ │ │ │ -71B00 Filename Length 001B (27) │ │ │ │ -71B02 Extra Length 001C (28) │ │ │ │ -71B04 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x71B04: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -71B1F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -71B21 Length 0009 (9) │ │ │ │ -71B23 Flags 03 (3) 'Modification Access' │ │ │ │ -71B24 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -71B28 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -71B2C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -71B2E Length 000B (11) │ │ │ │ -71B30 Version 01 (1) │ │ │ │ -71B31 UID Size 04 (4) │ │ │ │ -71B32 UID 00000000 (0) │ │ │ │ -71B36 GID Size 04 (4) │ │ │ │ -71B37 GID 00000000 (0) │ │ │ │ -71B3B PAYLOAD │ │ │ │ - │ │ │ │ -71C4E LOCAL HEADER #65 04034B50 (67324752) │ │ │ │ -71C52 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -71C53 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -71C54 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -71C56 Compression Method 0008 (8) 'Deflated' │ │ │ │ -71C58 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -71C5C CRC B802BFBB (3087187899) │ │ │ │ -71C60 Compressed Size 0000188E (6286) │ │ │ │ -71C64 Uncompressed Size 00008FAD (36781) │ │ │ │ -71C68 Filename Length 001D (29) │ │ │ │ -71C6A Extra Length 001C (28) │ │ │ │ -71C6C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x71C6C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -71C89 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -71C8B Length 0009 (9) │ │ │ │ -71C8D Flags 03 (3) 'Modification Access' │ │ │ │ -71C8E Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -71C92 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -71C96 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -71C98 Length 000B (11) │ │ │ │ -71C9A Version 01 (1) │ │ │ │ -71C9B UID Size 04 (4) │ │ │ │ -71C9C UID 00000000 (0) │ │ │ │ -71CA0 GID Size 04 (4) │ │ │ │ -71CA1 GID 00000000 (0) │ │ │ │ -71CA5 PAYLOAD │ │ │ │ - │ │ │ │ -73533 LOCAL HEADER #66 04034B50 (67324752) │ │ │ │ -73537 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -73538 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -73539 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -7353B Compression Method 0008 (8) 'Deflated' │ │ │ │ -7353D Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -73541 CRC 516C0F5D (1366036317) │ │ │ │ -73545 Compressed Size 0000164A (5706) │ │ │ │ -73549 Uncompressed Size 00003A9C (15004) │ │ │ │ -7354D Filename Length 0015 (21) │ │ │ │ -7354F Extra Length 001C (28) │ │ │ │ -73551 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x73551: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -73566 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -73568 Length 0009 (9) │ │ │ │ -7356A Flags 03 (3) 'Modification Access' │ │ │ │ -7356B Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -7356F Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -73573 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -73575 Length 000B (11) │ │ │ │ -73577 Version 01 (1) │ │ │ │ -73578 UID Size 04 (4) │ │ │ │ -73579 UID 00000000 (0) │ │ │ │ -7357D GID Size 04 (4) │ │ │ │ -7357E GID 00000000 (0) │ │ │ │ -73582 PAYLOAD │ │ │ │ - │ │ │ │ -74BCC LOCAL HEADER #67 04034B50 (67324752) │ │ │ │ -74BD0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -74BD1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -74BD2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -74BD4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -74BD6 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -74BDA CRC 3708308C (923283596) │ │ │ │ -74BDE Compressed Size 00003B4E (15182) │ │ │ │ -74BE2 Uncompressed Size 00011CC3 (72899) │ │ │ │ -74BE6 Filename Length 0016 (22) │ │ │ │ -74BE8 Extra Length 001C (28) │ │ │ │ -74BEA Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x74BEA: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -74C00 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -74C02 Length 0009 (9) │ │ │ │ -74C04 Flags 03 (3) 'Modification Access' │ │ │ │ -74C05 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -74C09 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -74C0D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -74C0F Length 000B (11) │ │ │ │ -74C11 Version 01 (1) │ │ │ │ -74C12 UID Size 04 (4) │ │ │ │ -74C13 UID 00000000 (0) │ │ │ │ -74C17 GID Size 04 (4) │ │ │ │ -74C18 GID 00000000 (0) │ │ │ │ -74C1C PAYLOAD │ │ │ │ - │ │ │ │ -7876A LOCAL HEADER #68 04034B50 (67324752) │ │ │ │ -7876E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -7876F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -78770 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -78772 Compression Method 0008 (8) 'Deflated' │ │ │ │ -78774 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -78778 CRC 7096BE5F (1888927327) │ │ │ │ -7877C Compressed Size 00003EC5 (16069) │ │ │ │ -78780 Uncompressed Size 0001C79C (116636) │ │ │ │ -78784 Filename Length 0019 (25) │ │ │ │ -78786 Extra Length 001C (28) │ │ │ │ -78788 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x78788: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -787A1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -787A3 Length 0009 (9) │ │ │ │ -787A5 Flags 03 (3) 'Modification Access' │ │ │ │ -787A6 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -787AA Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -787AE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -787B0 Length 000B (11) │ │ │ │ -787B2 Version 01 (1) │ │ │ │ -787B3 UID Size 04 (4) │ │ │ │ -787B4 UID 00000000 (0) │ │ │ │ -787B8 GID Size 04 (4) │ │ │ │ -787B9 GID 00000000 (0) │ │ │ │ -787BD PAYLOAD │ │ │ │ - │ │ │ │ -7C682 LOCAL HEADER #69 04034B50 (67324752) │ │ │ │ -7C686 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -7C687 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -7C688 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -7C68A Compression Method 0008 (8) 'Deflated' │ │ │ │ -7C68C Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -7C690 CRC 9FB04586 (2679129478) │ │ │ │ -7C694 Compressed Size 00000835 (2101) │ │ │ │ -7C698 Uncompressed Size 00003384 (13188) │ │ │ │ -7C69C Filename Length 0011 (17) │ │ │ │ -7C69E Extra Length 001C (28) │ │ │ │ -7C6A0 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x7C6A0: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -7C6B1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -7C6B3 Length 0009 (9) │ │ │ │ -7C6B5 Flags 03 (3) 'Modification Access' │ │ │ │ -7C6B6 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -7C6BA Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -7C6BE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -7C6C0 Length 000B (11) │ │ │ │ -7C6C2 Version 01 (1) │ │ │ │ -7C6C3 UID Size 04 (4) │ │ │ │ -7C6C4 UID 00000000 (0) │ │ │ │ -7C6C8 GID Size 04 (4) │ │ │ │ -7C6C9 GID 00000000 (0) │ │ │ │ -7C6CD PAYLOAD │ │ │ │ - │ │ │ │ -7CF02 LOCAL HEADER #70 04034B50 (67324752) │ │ │ │ -7CF06 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -7CF07 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -7CF08 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -7CF0A Compression Method 0008 (8) 'Deflated' │ │ │ │ -7CF0C Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -7CF10 CRC 48157666 (1209366118) │ │ │ │ -7CF14 Compressed Size 000051AB (20907) │ │ │ │ -7CF18 Uncompressed Size 0001FBE0 (130016) │ │ │ │ -7CF1C Filename Length 0015 (21) │ │ │ │ -7CF1E Extra Length 001C (28) │ │ │ │ -7CF20 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x7CF20: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -7CF35 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -7CF37 Length 0009 (9) │ │ │ │ -7CF39 Flags 03 (3) 'Modification Access' │ │ │ │ -7CF3A Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -7CF3E Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -7CF42 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -7CF44 Length 000B (11) │ │ │ │ -7CF46 Version 01 (1) │ │ │ │ -7CF47 UID Size 04 (4) │ │ │ │ -7CF48 UID 00000000 (0) │ │ │ │ -7CF4C GID Size 04 (4) │ │ │ │ -7CF4D GID 00000000 (0) │ │ │ │ -7CF51 PAYLOAD │ │ │ │ - │ │ │ │ -820FC LOCAL HEADER #71 04034B50 (67324752) │ │ │ │ -82100 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -82101 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -82102 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -82104 Compression Method 0008 (8) 'Deflated' │ │ │ │ -82106 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -8210A CRC 11C15F57 (297885527) │ │ │ │ -8210E Compressed Size 00001B09 (6921) │ │ │ │ -82112 Uncompressed Size 000081D0 (33232) │ │ │ │ -82116 Filename Length 0019 (25) │ │ │ │ -82118 Extra Length 001C (28) │ │ │ │ -8211A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8211A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -82133 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -82135 Length 0009 (9) │ │ │ │ -82137 Flags 03 (3) 'Modification Access' │ │ │ │ -82138 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -8213C Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -82140 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -82142 Length 000B (11) │ │ │ │ -82144 Version 01 (1) │ │ │ │ -82145 UID Size 04 (4) │ │ │ │ -82146 UID 00000000 (0) │ │ │ │ -8214A GID Size 04 (4) │ │ │ │ -8214B GID 00000000 (0) │ │ │ │ -8214F PAYLOAD │ │ │ │ - │ │ │ │ -83C58 LOCAL HEADER #72 04034B50 (67324752) │ │ │ │ -83C5C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -83C5D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -83C5E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -83C60 Compression Method 0008 (8) 'Deflated' │ │ │ │ -83C62 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -83C66 CRC ECB3AEBD (3971198653) │ │ │ │ -83C6A Compressed Size 00000D99 (3481) │ │ │ │ -83C6E Uncompressed Size 00002EA0 (11936) │ │ │ │ -83C72 Filename Length 0018 (24) │ │ │ │ -83C74 Extra Length 001C (28) │ │ │ │ -83C76 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x83C76: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -83C8E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -83C90 Length 0009 (9) │ │ │ │ -83C92 Flags 03 (3) 'Modification Access' │ │ │ │ -83C93 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -83C97 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -83C9B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -83C9D Length 000B (11) │ │ │ │ -83C9F Version 01 (1) │ │ │ │ -83CA0 UID Size 04 (4) │ │ │ │ -83CA1 UID 00000000 (0) │ │ │ │ -83CA5 GID Size 04 (4) │ │ │ │ -83CA6 GID 00000000 (0) │ │ │ │ -83CAA PAYLOAD │ │ │ │ - │ │ │ │ -84A43 LOCAL HEADER #73 04034B50 (67324752) │ │ │ │ -84A47 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -84A48 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -84A49 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -84A4B Compression Method 0008 (8) 'Deflated' │ │ │ │ -84A4D Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -84A51 CRC 037E3A9E (58604190) │ │ │ │ -84A55 Compressed Size 000001E1 (481) │ │ │ │ -84A59 Uncompressed Size 00000324 (804) │ │ │ │ -84A5D Filename Length 0011 (17) │ │ │ │ -84A5F Extra Length 001C (28) │ │ │ │ -84A61 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x84A61: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -84A72 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -84A74 Length 0009 (9) │ │ │ │ -84A76 Flags 03 (3) 'Modification Access' │ │ │ │ -84A77 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -84A7B Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -84A7F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -84A81 Length 000B (11) │ │ │ │ -84A83 Version 01 (1) │ │ │ │ -84A84 UID Size 04 (4) │ │ │ │ -84A85 UID 00000000 (0) │ │ │ │ -84A89 GID Size 04 (4) │ │ │ │ -84A8A GID 00000000 (0) │ │ │ │ -84A8E PAYLOAD │ │ │ │ - │ │ │ │ -84C6F LOCAL HEADER #74 04034B50 (67324752) │ │ │ │ -84C73 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -84C74 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -84C75 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -84C77 Compression Method 0008 (8) 'Deflated' │ │ │ │ -84C79 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -84C7D CRC 55187787 (1427666823) │ │ │ │ -84C81 Compressed Size 000006C3 (1731) │ │ │ │ -84C85 Uncompressed Size 0000143A (5178) │ │ │ │ -84C89 Filename Length 0019 (25) │ │ │ │ -84C8B Extra Length 001C (28) │ │ │ │ -84C8D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x84C8D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -84CA6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -84CA8 Length 0009 (9) │ │ │ │ -84CAA Flags 03 (3) 'Modification Access' │ │ │ │ -84CAB Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -84CAF Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -84CB3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -84CB5 Length 000B (11) │ │ │ │ -84CB7 Version 01 (1) │ │ │ │ -84CB8 UID Size 04 (4) │ │ │ │ -84CB9 UID 00000000 (0) │ │ │ │ -84CBD GID Size 04 (4) │ │ │ │ -84CBE GID 00000000 (0) │ │ │ │ -84CC2 PAYLOAD │ │ │ │ - │ │ │ │ -85385 LOCAL HEADER #75 04034B50 (67324752) │ │ │ │ -85389 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8538A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8538B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8538D Compression Method 0008 (8) 'Deflated' │ │ │ │ -8538F Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -85393 CRC B80689A0 (3087436192) │ │ │ │ -85397 Compressed Size 00001EAE (7854) │ │ │ │ -8539B Uncompressed Size 0000CA81 (51841) │ │ │ │ -8539F Filename Length 0018 (24) │ │ │ │ -853A1 Extra Length 001C (28) │ │ │ │ -853A3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x853A3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -853BB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -853BD Length 0009 (9) │ │ │ │ -853BF Flags 03 (3) 'Modification Access' │ │ │ │ -853C0 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -853C4 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -853C8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -853CA Length 000B (11) │ │ │ │ -853CC Version 01 (1) │ │ │ │ -853CD UID Size 04 (4) │ │ │ │ -853CE UID 00000000 (0) │ │ │ │ -853D2 GID Size 04 (4) │ │ │ │ -853D3 GID 00000000 (0) │ │ │ │ -853D7 PAYLOAD │ │ │ │ - │ │ │ │ -87285 LOCAL HEADER #76 04034B50 (67324752) │ │ │ │ -87289 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8728A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8728B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8728D Compression Method 0008 (8) 'Deflated' │ │ │ │ -8728F Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -87293 CRC 70CE7879 (1892579449) │ │ │ │ -87297 Compressed Size 00001BEA (7146) │ │ │ │ -8729B Uncompressed Size 0000C5EA (50666) │ │ │ │ -8729F Filename Length 0012 (18) │ │ │ │ -872A1 Extra Length 001C (28) │ │ │ │ -872A3 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x872A3: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -872B5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -872B7 Length 0009 (9) │ │ │ │ -872B9 Flags 03 (3) 'Modification Access' │ │ │ │ -872BA Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -872BE Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -872C2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -872C4 Length 000B (11) │ │ │ │ -872C6 Version 01 (1) │ │ │ │ -872C7 UID Size 04 (4) │ │ │ │ -872C8 UID 00000000 (0) │ │ │ │ -872CC GID Size 04 (4) │ │ │ │ -872CD GID 00000000 (0) │ │ │ │ -872D1 PAYLOAD │ │ │ │ - │ │ │ │ -88EBB LOCAL HEADER #77 04034B50 (67324752) │ │ │ │ -88EBF Extract Zip Spec 14 (20) '2.0' │ │ │ │ -88EC0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -88EC1 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -88EC3 Compression Method 0008 (8) 'Deflated' │ │ │ │ -88EC5 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -88EC9 CRC A289EFE2 (2726948834) │ │ │ │ -88ECD Compressed Size 00001E0F (7695) │ │ │ │ -88ED1 Uncompressed Size 00008804 (34820) │ │ │ │ -88ED5 Filename Length 0016 (22) │ │ │ │ -88ED7 Extra Length 001C (28) │ │ │ │ -88ED9 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x88ED9: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -88EEF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -88EF1 Length 0009 (9) │ │ │ │ -88EF3 Flags 03 (3) 'Modification Access' │ │ │ │ -88EF4 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -88EF8 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -88EFC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -88EFE Length 000B (11) │ │ │ │ -88F00 Version 01 (1) │ │ │ │ -88F01 UID Size 04 (4) │ │ │ │ -88F02 UID 00000000 (0) │ │ │ │ -88F06 GID Size 04 (4) │ │ │ │ -88F07 GID 00000000 (0) │ │ │ │ -88F0B PAYLOAD │ │ │ │ - │ │ │ │ -8AD1A LOCAL HEADER #78 04034B50 (67324752) │ │ │ │ -8AD1E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8AD1F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8AD20 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8AD22 Compression Method 0008 (8) 'Deflated' │ │ │ │ -8AD24 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -8AD28 CRC A54D678B (2773313419) │ │ │ │ -8AD2C Compressed Size 000029A3 (10659) │ │ │ │ -8AD30 Uncompressed Size 0000D050 (53328) │ │ │ │ -8AD34 Filename Length 001A (26) │ │ │ │ -8AD36 Extra Length 001C (28) │ │ │ │ -8AD38 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8AD38: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8AD52 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -8AD54 Length 0009 (9) │ │ │ │ -8AD56 Flags 03 (3) 'Modification Access' │ │ │ │ -8AD57 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -8AD5B Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -8AD5F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8AD61 Length 000B (11) │ │ │ │ -8AD63 Version 01 (1) │ │ │ │ -8AD64 UID Size 04 (4) │ │ │ │ -8AD65 UID 00000000 (0) │ │ │ │ -8AD69 GID Size 04 (4) │ │ │ │ -8AD6A GID 00000000 (0) │ │ │ │ -8AD6E PAYLOAD │ │ │ │ - │ │ │ │ -8D711 LOCAL HEADER #79 04034B50 (67324752) │ │ │ │ -8D715 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8D716 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8D717 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8D719 Compression Method 0008 (8) 'Deflated' │ │ │ │ -8D71B Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -8D71F CRC 80E743AF (2162639791) │ │ │ │ -8D723 Compressed Size 000009AE (2478) │ │ │ │ -8D727 Uncompressed Size 00001DB7 (7607) │ │ │ │ -8D72B Filename Length 0018 (24) │ │ │ │ -8D72D Extra Length 001C (28) │ │ │ │ -8D72F Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8D72F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8D747 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -8D749 Length 0009 (9) │ │ │ │ -8D74B Flags 03 (3) 'Modification Access' │ │ │ │ -8D74C Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -8D750 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -8D754 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8D756 Length 000B (11) │ │ │ │ -8D758 Version 01 (1) │ │ │ │ -8D759 UID Size 04 (4) │ │ │ │ -8D75A UID 00000000 (0) │ │ │ │ -8D75E GID Size 04 (4) │ │ │ │ -8D75F GID 00000000 (0) │ │ │ │ -8D763 PAYLOAD │ │ │ │ - │ │ │ │ -8E111 LOCAL HEADER #80 04034B50 (67324752) │ │ │ │ -8E115 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8E116 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8E117 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8E119 Compression Method 0008 (8) 'Deflated' │ │ │ │ -8E11B Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -8E11F CRC F5E2129F (4125233823) │ │ │ │ -8E123 Compressed Size 000016BC (5820) │ │ │ │ -8E127 Uncompressed Size 000016CD (5837) │ │ │ │ -8E12B Filename Length 0015 (21) │ │ │ │ -8E12D Extra Length 001C (28) │ │ │ │ -8E12F Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8E12F: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8E144 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -8E146 Length 0009 (9) │ │ │ │ -8E148 Flags 03 (3) 'Modification Access' │ │ │ │ -8E149 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -8E14D Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -8E151 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8E153 Length 000B (11) │ │ │ │ -8E155 Version 01 (1) │ │ │ │ -8E156 UID Size 04 (4) │ │ │ │ -8E157 UID 00000000 (0) │ │ │ │ -8E15B GID Size 04 (4) │ │ │ │ -8E15C GID 00000000 (0) │ │ │ │ -8E160 PAYLOAD │ │ │ │ - │ │ │ │ -8F81C LOCAL HEADER #81 04034B50 (67324752) │ │ │ │ -8F820 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8F821 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8F822 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8F824 Compression Method 0008 (8) 'Deflated' │ │ │ │ -8F826 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -8F82A CRC F5E2129F (4125233823) │ │ │ │ -8F82E Compressed Size 000016BC (5820) │ │ │ │ -8F832 Uncompressed Size 000016CD (5837) │ │ │ │ -8F836 Filename Length 001C (28) │ │ │ │ -8F838 Extra Length 001C (28) │ │ │ │ -8F83A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8F83A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8F856 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -8F858 Length 0009 (9) │ │ │ │ -8F85A Flags 03 (3) 'Modification Access' │ │ │ │ -8F85B Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -8F85F Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -8F863 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8F865 Length 000B (11) │ │ │ │ -8F867 Version 01 (1) │ │ │ │ -8F868 UID Size 04 (4) │ │ │ │ -8F869 UID 00000000 (0) │ │ │ │ -8F86D GID Size 04 (4) │ │ │ │ -8F86E GID 00000000 (0) │ │ │ │ -8F872 PAYLOAD │ │ │ │ - │ │ │ │ -90F2E LOCAL HEADER #82 04034B50 (67324752) │ │ │ │ -90F32 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -90F33 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -90F34 General Purpose Flag 0000 (0) │ │ │ │ -90F36 Compression Method 0000 (0) 'Stored' │ │ │ │ -90F38 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -90F3C CRC FC95F24B (4237685323) │ │ │ │ -90F40 Compressed Size 00001B84 (7044) │ │ │ │ -90F44 Uncompressed Size 00001B84 (7044) │ │ │ │ -90F48 Filename Length 0016 (22) │ │ │ │ -90F4A Extra Length 001C (28) │ │ │ │ -90F4C Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x90F4C: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -90F62 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -90F64 Length 0009 (9) │ │ │ │ -90F66 Flags 03 (3) 'Modification Access' │ │ │ │ -90F67 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -90F6B Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -90F6F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -90F71 Length 000B (11) │ │ │ │ -90F73 Version 01 (1) │ │ │ │ -90F74 UID Size 04 (4) │ │ │ │ -90F75 UID 00000000 (0) │ │ │ │ -90F79 GID Size 04 (4) │ │ │ │ -90F7A GID 00000000 (0) │ │ │ │ -90F7E PAYLOAD │ │ │ │ - │ │ │ │ -92B02 LOCAL HEADER #83 04034B50 (67324752) │ │ │ │ -92B06 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -92B07 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -92B08 General Purpose Flag 0000 (0) │ │ │ │ -92B0A Compression Method 0000 (0) 'Stored' │ │ │ │ -92B0C Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -92B10 CRC D0D71F86 (3503759238) │ │ │ │ -92B14 Compressed Size 00000B7B (2939) │ │ │ │ -92B18 Uncompressed Size 00000B7B (2939) │ │ │ │ -92B1C Filename Length 0016 (22) │ │ │ │ -92B1E Extra Length 001C (28) │ │ │ │ -92B20 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x92B20: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -92B36 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -92B38 Length 0009 (9) │ │ │ │ -92B3A Flags 03 (3) 'Modification Access' │ │ │ │ -92B3B Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -92B3F Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -92B43 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -92B45 Length 000B (11) │ │ │ │ -92B47 Version 01 (1) │ │ │ │ -92B48 UID Size 04 (4) │ │ │ │ -92B49 UID 00000000 (0) │ │ │ │ -92B4D GID Size 04 (4) │ │ │ │ -92B4E GID 00000000 (0) │ │ │ │ -92B52 PAYLOAD │ │ │ │ - │ │ │ │ -936CD LOCAL HEADER #84 04034B50 (67324752) │ │ │ │ -936D1 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -936D2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -936D3 General Purpose Flag 0000 (0) │ │ │ │ -936D5 Compression Method 0000 (0) 'Stored' │ │ │ │ -936D7 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -936DB CRC FFF9C4D2 (4294558930) │ │ │ │ -936DF Compressed Size 0000138F (5007) │ │ │ │ -936E3 Uncompressed Size 0000138F (5007) │ │ │ │ -936E7 Filename Length 0016 (22) │ │ │ │ -936E9 Extra Length 001C (28) │ │ │ │ -936EB Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x936EB: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -93701 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -93703 Length 0009 (9) │ │ │ │ -93705 Flags 03 (3) 'Modification Access' │ │ │ │ -93706 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9370A Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9370E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -93710 Length 000B (11) │ │ │ │ -93712 Version 01 (1) │ │ │ │ -93713 UID Size 04 (4) │ │ │ │ -93714 UID 00000000 (0) │ │ │ │ -93718 GID Size 04 (4) │ │ │ │ -93719 GID 00000000 (0) │ │ │ │ -9371D PAYLOAD │ │ │ │ - │ │ │ │ -94AAC LOCAL HEADER #85 04034B50 (67324752) │ │ │ │ -94AB0 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -94AB1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -94AB2 General Purpose Flag 0000 (0) │ │ │ │ -94AB4 Compression Method 0000 (0) 'Stored' │ │ │ │ -94AB6 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -94ABA CRC A1037E8E (2701360782) │ │ │ │ -94ABE Compressed Size 0000145E (5214) │ │ │ │ -94AC2 Uncompressed Size 0000145E (5214) │ │ │ │ -94AC6 Filename Length 0016 (22) │ │ │ │ -94AC8 Extra Length 001C (28) │ │ │ │ -94ACA Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x94ACA: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -94AE0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -94AE2 Length 0009 (9) │ │ │ │ -94AE4 Flags 03 (3) 'Modification Access' │ │ │ │ -94AE5 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -94AE9 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -94AED Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -94AEF Length 000B (11) │ │ │ │ -94AF1 Version 01 (1) │ │ │ │ -94AF2 UID Size 04 (4) │ │ │ │ -94AF3 UID 00000000 (0) │ │ │ │ -94AF7 GID Size 04 (4) │ │ │ │ -94AF8 GID 00000000 (0) │ │ │ │ -94AFC PAYLOAD │ │ │ │ - │ │ │ │ -95F5A LOCAL HEADER #86 04034B50 (67324752) │ │ │ │ -95F5E Extract Zip Spec 0A (10) '1.0' │ │ │ │ -95F5F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -95F60 General Purpose Flag 0000 (0) │ │ │ │ -95F62 Compression Method 0000 (0) 'Stored' │ │ │ │ -95F64 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -95F68 CRC 5E9E64F1 (1587438833) │ │ │ │ -95F6C Compressed Size 000008EC (2284) │ │ │ │ -95F70 Uncompressed Size 000008EC (2284) │ │ │ │ -95F74 Filename Length 0016 (22) │ │ │ │ -95F76 Extra Length 001C (28) │ │ │ │ -95F78 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x95F78: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -95F8E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -95F90 Length 0009 (9) │ │ │ │ -95F92 Flags 03 (3) 'Modification Access' │ │ │ │ -95F93 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -95F97 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -95F9B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -95F9D Length 000B (11) │ │ │ │ -95F9F Version 01 (1) │ │ │ │ -95FA0 UID Size 04 (4) │ │ │ │ -95FA1 UID 00000000 (0) │ │ │ │ -95FA5 GID Size 04 (4) │ │ │ │ -95FA6 GID 00000000 (0) │ │ │ │ -95FAA PAYLOAD │ │ │ │ - │ │ │ │ -96896 LOCAL HEADER #87 04034B50 (67324752) │ │ │ │ -9689A Extract Zip Spec 0A (10) '1.0' │ │ │ │ -9689B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9689C General Purpose Flag 0000 (0) │ │ │ │ -9689E Compression Method 0000 (0) 'Stored' │ │ │ │ -968A0 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -968A4 CRC 42E340AB (1122189483) │ │ │ │ -968A8 Compressed Size 00001F2E (7982) │ │ │ │ -968AC Uncompressed Size 00001F2E (7982) │ │ │ │ -968B0 Filename Length 001E (30) │ │ │ │ -968B2 Extra Length 001C (28) │ │ │ │ -968B4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x968B4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -968D2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -968D4 Length 0009 (9) │ │ │ │ -968D6 Flags 03 (3) 'Modification Access' │ │ │ │ -968D7 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -968DB Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -968DF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -968E1 Length 000B (11) │ │ │ │ -968E3 Version 01 (1) │ │ │ │ -968E4 UID Size 04 (4) │ │ │ │ -968E5 UID 00000000 (0) │ │ │ │ -968E9 GID Size 04 (4) │ │ │ │ -968EA GID 00000000 (0) │ │ │ │ -968EE PAYLOAD │ │ │ │ - │ │ │ │ -9881C LOCAL HEADER #88 04034B50 (67324752) │ │ │ │ -98820 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -98821 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -98822 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -98824 Compression Method 0008 (8) 'Deflated' │ │ │ │ -98826 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9882A CRC A2C99D83 (2731122051) │ │ │ │ -9882E Compressed Size 00004096 (16534) │ │ │ │ -98832 Uncompressed Size 0001971B (104219) │ │ │ │ -98836 Filename Length 001A (26) │ │ │ │ -98838 Extra Length 001C (28) │ │ │ │ -9883A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9883A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -98854 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -98856 Length 0009 (9) │ │ │ │ -98858 Flags 03 (3) 'Modification Access' │ │ │ │ -98859 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9885D Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -98861 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -98863 Length 000B (11) │ │ │ │ -98865 Version 01 (1) │ │ │ │ -98866 UID Size 04 (4) │ │ │ │ -98867 UID 00000000 (0) │ │ │ │ -9886B GID Size 04 (4) │ │ │ │ -9886C GID 00000000 (0) │ │ │ │ -98870 PAYLOAD │ │ │ │ - │ │ │ │ -9C906 LOCAL HEADER #89 04034B50 (67324752) │ │ │ │ -9C90A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9C90B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9C90C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9C90E Compression Method 0008 (8) 'Deflated' │ │ │ │ -9C910 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9C914 CRC 57F9A5F4 (1475978740) │ │ │ │ -9C918 Compressed Size 000029CF (10703) │ │ │ │ -9C91C Uncompressed Size 0000BB3A (47930) │ │ │ │ -9C920 Filename Length 0018 (24) │ │ │ │ -9C922 Extra Length 001C (28) │ │ │ │ -9C924 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9C924: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9C93C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9C93E Length 0009 (9) │ │ │ │ -9C940 Flags 03 (3) 'Modification Access' │ │ │ │ -9C941 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9C945 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9C949 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9C94B Length 000B (11) │ │ │ │ -9C94D Version 01 (1) │ │ │ │ -9C94E UID Size 04 (4) │ │ │ │ -9C94F UID 00000000 (0) │ │ │ │ -9C953 GID Size 04 (4) │ │ │ │ -9C954 GID 00000000 (0) │ │ │ │ -9C958 PAYLOAD │ │ │ │ - │ │ │ │ -9F327 LOCAL HEADER #90 04034B50 (67324752) │ │ │ │ -9F32B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F32C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F32D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F32F Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F331 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F335 CRC DCB3B516 (3702764822) │ │ │ │ -9F339 Compressed Size 000000AE (174) │ │ │ │ -9F33D Uncompressed Size 000000FC (252) │ │ │ │ -9F341 Filename Length 0016 (22) │ │ │ │ -9F343 Extra Length 001C (28) │ │ │ │ -9F345 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F345: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F35B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F35D Length 0009 (9) │ │ │ │ -9F35F Flags 03 (3) 'Modification Access' │ │ │ │ -9F360 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F364 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F368 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F36A Length 000B (11) │ │ │ │ -9F36C Version 01 (1) │ │ │ │ -9F36D UID Size 04 (4) │ │ │ │ -9F36E UID 00000000 (0) │ │ │ │ -9F372 GID Size 04 (4) │ │ │ │ -9F373 GID 00000000 (0) │ │ │ │ -9F377 PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ +04B65 LOCAL HEADER #5 04034B50 (67324752) │ │ │ │ +04B69 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +04B6A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +04B6B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +04B6D Compression Method 0008 (8) 'Deflated' │ │ │ │ +04B6F Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +04B73 CRC AFEB34EA (2951427306) │ │ │ │ +04B77 Compressed Size 000003F0 (1008) │ │ │ │ +04B7B Uncompressed Size 00000877 (2167) │ │ │ │ +04B7F Filename Length 0014 (20) │ │ │ │ +04B81 Extra Length 001C (28) │ │ │ │ +04B83 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4B83: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +04B97 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +04B99 Length 0009 (9) │ │ │ │ +04B9B Flags 03 (3) 'Modification Access' │ │ │ │ +04B9C Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +04BA0 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +04BA4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +04BA6 Length 000B (11) │ │ │ │ +04BA8 Version 01 (1) │ │ │ │ +04BA9 UID Size 04 (4) │ │ │ │ +04BAA UID 00000000 (0) │ │ │ │ +04BAE GID Size 04 (4) │ │ │ │ +04BAF GID 00000000 (0) │ │ │ │ +04BB3 PAYLOAD │ │ │ │ + │ │ │ │ +04FA3 LOCAL HEADER #6 04034B50 (67324752) │ │ │ │ +04FA7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +04FA8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +04FA9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +04FAB Compression Method 0008 (8) 'Deflated' │ │ │ │ +04FAD Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +04FB1 CRC 21AF85A7 (565151143) │ │ │ │ +04FB5 Compressed Size 000001AE (430) │ │ │ │ +04FB9 Uncompressed Size 000002FE (766) │ │ │ │ +04FBD Filename Length 0011 (17) │ │ │ │ +04FBF Extra Length 001C (28) │ │ │ │ +04FC1 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4FC1: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +04FD2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +04FD4 Length 0009 (9) │ │ │ │ +04FD6 Flags 03 (3) 'Modification Access' │ │ │ │ +04FD7 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +04FDB Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +04FDF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +04FE1 Length 000B (11) │ │ │ │ +04FE3 Version 01 (1) │ │ │ │ +04FE4 UID Size 04 (4) │ │ │ │ +04FE5 UID 00000000 (0) │ │ │ │ +04FE9 GID Size 04 (4) │ │ │ │ +04FEA GID 00000000 (0) │ │ │ │ +04FEE PAYLOAD │ │ │ │ + │ │ │ │ +0519C LOCAL HEADER #7 04034B50 (67324752) │ │ │ │ +051A0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +051A1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +051A2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +051A4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +051A6 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +051AA CRC 9BD5D9C7 (2614483399) │ │ │ │ +051AE Compressed Size 000020C5 (8389) │ │ │ │ +051B2 Uncompressed Size 0000B4B1 (46257) │ │ │ │ +051B6 Filename Length 001B (27) │ │ │ │ +051B8 Extra Length 001C (28) │ │ │ │ +051BA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x51BA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +051D5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +051D7 Length 0009 (9) │ │ │ │ +051D9 Flags 03 (3) 'Modification Access' │ │ │ │ +051DA Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +051DE Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +051E2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +051E4 Length 000B (11) │ │ │ │ +051E6 Version 01 (1) │ │ │ │ +051E7 UID Size 04 (4) │ │ │ │ +051E8 UID 00000000 (0) │ │ │ │ +051EC GID Size 04 (4) │ │ │ │ +051ED GID 00000000 (0) │ │ │ │ +051F1 PAYLOAD │ │ │ │ + │ │ │ │ +072B6 LOCAL HEADER #8 04034B50 (67324752) │ │ │ │ +072BA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +072BB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +072BC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +072BE Compression Method 0008 (8) 'Deflated' │ │ │ │ +072C0 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +072C4 CRC 2D371F1E (758587166) │ │ │ │ +072C8 Compressed Size 00000E70 (3696) │ │ │ │ +072CC Uncompressed Size 000030B3 (12467) │ │ │ │ +072D0 Filename Length 001D (29) │ │ │ │ +072D2 Extra Length 001C (28) │ │ │ │ +072D4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x72D4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +072F1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +072F3 Length 0009 (9) │ │ │ │ +072F5 Flags 03 (3) 'Modification Access' │ │ │ │ +072F6 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +072FA Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +072FE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +07300 Length 000B (11) │ │ │ │ +07302 Version 01 (1) │ │ │ │ +07303 UID Size 04 (4) │ │ │ │ +07304 UID 00000000 (0) │ │ │ │ +07308 GID Size 04 (4) │ │ │ │ +07309 GID 00000000 (0) │ │ │ │ +0730D PAYLOAD │ │ │ │ + │ │ │ │ +0817D LOCAL HEADER #9 04034B50 (67324752) │ │ │ │ +08181 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +08182 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +08183 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +08185 Compression Method 0008 (8) 'Deflated' │ │ │ │ +08187 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +0818B CRC 7903D0CB (2030293195) │ │ │ │ +0818F Compressed Size 00000973 (2419) │ │ │ │ +08193 Uncompressed Size 00001CB3 (7347) │ │ │ │ +08197 Filename Length 0019 (25) │ │ │ │ +08199 Extra Length 001C (28) │ │ │ │ +0819B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x819B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +081B4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +081B6 Length 0009 (9) │ │ │ │ +081B8 Flags 03 (3) 'Modification Access' │ │ │ │ +081B9 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +081BD Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +081C1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +081C3 Length 000B (11) │ │ │ │ +081C5 Version 01 (1) │ │ │ │ +081C6 UID Size 04 (4) │ │ │ │ +081C7 UID 00000000 (0) │ │ │ │ +081CB GID Size 04 (4) │ │ │ │ +081CC GID 00000000 (0) │ │ │ │ +081D0 PAYLOAD │ │ │ │ + │ │ │ │ +08B43 LOCAL HEADER #10 04034B50 (67324752) │ │ │ │ +08B47 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +08B48 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +08B49 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +08B4B Compression Method 0008 (8) 'Deflated' │ │ │ │ +08B4D Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +08B51 CRC 3959782C (962164780) │ │ │ │ +08B55 Compressed Size 00003880 (14464) │ │ │ │ +08B59 Uncompressed Size 0000F7F5 (63477) │ │ │ │ +08B5D Filename Length 0015 (21) │ │ │ │ +08B5F Extra Length 001C (28) │ │ │ │ +08B61 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8B61: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +08B76 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +08B78 Length 0009 (9) │ │ │ │ +08B7A Flags 03 (3) 'Modification Access' │ │ │ │ +08B7B Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +08B7F Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +08B83 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +08B85 Length 000B (11) │ │ │ │ +08B87 Version 01 (1) │ │ │ │ +08B88 UID Size 04 (4) │ │ │ │ +08B89 UID 00000000 (0) │ │ │ │ +08B8D GID Size 04 (4) │ │ │ │ +08B8E GID 00000000 (0) │ │ │ │ +08B92 PAYLOAD │ │ │ │ + │ │ │ │ +0C412 LOCAL HEADER #11 04034B50 (67324752) │ │ │ │ +0C416 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +0C417 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +0C418 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +0C41A Compression Method 0008 (8) 'Deflated' │ │ │ │ +0C41C Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +0C420 CRC 430C8466 (1124893798) │ │ │ │ +0C424 Compressed Size 0000AB09 (43785) │ │ │ │ +0C428 Uncompressed Size 0003E052 (254034) │ │ │ │ +0C42C Filename Length 0012 (18) │ │ │ │ +0C42E Extra Length 001C (28) │ │ │ │ +0C430 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC430: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +0C442 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +0C444 Length 0009 (9) │ │ │ │ +0C446 Flags 03 (3) 'Modification Access' │ │ │ │ +0C447 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +0C44B Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +0C44F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +0C451 Length 000B (11) │ │ │ │ +0C453 Version 01 (1) │ │ │ │ +0C454 UID Size 04 (4) │ │ │ │ +0C455 UID 00000000 (0) │ │ │ │ +0C459 GID Size 04 (4) │ │ │ │ +0C45A GID 00000000 (0) │ │ │ │ +0C45E PAYLOAD │ │ │ │ + │ │ │ │ +16F67 LOCAL HEADER #12 04034B50 (67324752) │ │ │ │ +16F6B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +16F6C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +16F6D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +16F6F Compression Method 0008 (8) 'Deflated' │ │ │ │ +16F71 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +16F75 CRC 1EC6C3B8 (516342712) │ │ │ │ +16F79 Compressed Size 00003B0F (15119) │ │ │ │ +16F7D Uncompressed Size 0001B46D (111725) │ │ │ │ +16F81 Filename Length 0015 (21) │ │ │ │ +16F83 Extra Length 001C (28) │ │ │ │ +16F85 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x16F85: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +16F9A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +16F9C Length 0009 (9) │ │ │ │ +16F9E Flags 03 (3) 'Modification Access' │ │ │ │ +16F9F Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +16FA3 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +16FA7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +16FA9 Length 000B (11) │ │ │ │ +16FAB Version 01 (1) │ │ │ │ +16FAC UID Size 04 (4) │ │ │ │ +16FAD UID 00000000 (0) │ │ │ │ +16FB1 GID Size 04 (4) │ │ │ │ +16FB2 GID 00000000 (0) │ │ │ │ +16FB6 PAYLOAD │ │ │ │ + │ │ │ │ +1AAC5 LOCAL HEADER #13 04034B50 (67324752) │ │ │ │ +1AAC9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +1AACA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +1AACB General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +1AACD Compression Method 0008 (8) 'Deflated' │ │ │ │ +1AACF Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +1AAD3 CRC AE44C2B4 (2923741876) │ │ │ │ +1AAD7 Compressed Size 000091C5 (37317) │ │ │ │ +1AADB Uncompressed Size 0003D9DE (252382) │ │ │ │ +1AADF Filename Length 0014 (20) │ │ │ │ +1AAE1 Extra Length 001C (28) │ │ │ │ +1AAE3 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x1AAE3: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +1AAF7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +1AAF9 Length 0009 (9) │ │ │ │ +1AAFB Flags 03 (3) 'Modification Access' │ │ │ │ +1AAFC Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +1AB00 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +1AB04 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +1AB06 Length 000B (11) │ │ │ │ +1AB08 Version 01 (1) │ │ │ │ +1AB09 UID Size 04 (4) │ │ │ │ +1AB0A UID 00000000 (0) │ │ │ │ +1AB0E GID Size 04 (4) │ │ │ │ +1AB0F GID 00000000 (0) │ │ │ │ +1AB13 PAYLOAD │ │ │ │ + │ │ │ │ +23CD8 LOCAL HEADER #14 04034B50 (67324752) │ │ │ │ +23CDC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +23CDD Extract OS 00 (0) 'MS-DOS' │ │ │ │ +23CDE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +23CE0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +23CE2 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +23CE6 CRC 0F5A0761 (257558369) │ │ │ │ +23CEA Compressed Size 00002A69 (10857) │ │ │ │ +23CEE Uncompressed Size 00011520 (70944) │ │ │ │ +23CF2 Filename Length 0016 (22) │ │ │ │ +23CF4 Extra Length 001C (28) │ │ │ │ +23CF6 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x23CF6: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +23D0C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +23D0E Length 0009 (9) │ │ │ │ +23D10 Flags 03 (3) 'Modification Access' │ │ │ │ +23D11 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +23D15 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +23D19 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +23D1B Length 000B (11) │ │ │ │ +23D1D Version 01 (1) │ │ │ │ +23D1E UID Size 04 (4) │ │ │ │ +23D1F UID 00000000 (0) │ │ │ │ +23D23 GID Size 04 (4) │ │ │ │ +23D24 GID 00000000 (0) │ │ │ │ +23D28 PAYLOAD │ │ │ │ + │ │ │ │ +26791 LOCAL HEADER #15 04034B50 (67324752) │ │ │ │ +26795 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +26796 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +26797 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +26799 Compression Method 0008 (8) 'Deflated' │ │ │ │ +2679B Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +2679F CRC A70066B2 (2801821362) │ │ │ │ +267A3 Compressed Size 000014D7 (5335) │ │ │ │ +267A7 Uncompressed Size 0000518E (20878) │ │ │ │ +267AB Filename Length 001D (29) │ │ │ │ +267AD Extra Length 001C (28) │ │ │ │ +267AF Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x267AF: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +267CC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +267CE Length 0009 (9) │ │ │ │ +267D0 Flags 03 (3) 'Modification Access' │ │ │ │ +267D1 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +267D5 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +267D9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +267DB Length 000B (11) │ │ │ │ +267DD Version 01 (1) │ │ │ │ +267DE UID Size 04 (4) │ │ │ │ +267DF UID 00000000 (0) │ │ │ │ +267E3 GID Size 04 (4) │ │ │ │ +267E4 GID 00000000 (0) │ │ │ │ +267E8 PAYLOAD │ │ │ │ + │ │ │ │ +27CBF LOCAL HEADER #16 04034B50 (67324752) │ │ │ │ +27CC3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +27CC4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +27CC5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +27CC7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +27CC9 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +27CCD CRC 82F97239 (2197385785) │ │ │ │ +27CD1 Compressed Size 000039AB (14763) │ │ │ │ +27CD5 Uncompressed Size 0000F080 (61568) │ │ │ │ +27CD9 Filename Length 001C (28) │ │ │ │ +27CDB Extra Length 001C (28) │ │ │ │ +27CDD Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x27CDD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +27CF9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +27CFB Length 0009 (9) │ │ │ │ +27CFD Flags 03 (3) 'Modification Access' │ │ │ │ +27CFE Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +27D02 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +27D06 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +27D08 Length 000B (11) │ │ │ │ +27D0A Version 01 (1) │ │ │ │ +27D0B UID Size 04 (4) │ │ │ │ +27D0C UID 00000000 (0) │ │ │ │ +27D10 GID Size 04 (4) │ │ │ │ +27D11 GID 00000000 (0) │ │ │ │ +27D15 PAYLOAD │ │ │ │ + │ │ │ │ +2B6C0 LOCAL HEADER #17 04034B50 (67324752) │ │ │ │ +2B6C4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +2B6C5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +2B6C6 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +2B6C8 Compression Method 0008 (8) 'Deflated' │ │ │ │ +2B6CA Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +2B6CE CRC 7A88ED58 (2055794008) │ │ │ │ +2B6D2 Compressed Size 000006A3 (1699) │ │ │ │ +2B6D6 Uncompressed Size 000011F5 (4597) │ │ │ │ +2B6DA Filename Length 001C (28) │ │ │ │ +2B6DC Extra Length 001C (28) │ │ │ │ +2B6DE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x2B6DE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +2B6FA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +2B6FC Length 0009 (9) │ │ │ │ +2B6FE Flags 03 (3) 'Modification Access' │ │ │ │ +2B6FF Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +2B703 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +2B707 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +2B709 Length 000B (11) │ │ │ │ +2B70B Version 01 (1) │ │ │ │ +2B70C UID Size 04 (4) │ │ │ │ +2B70D UID 00000000 (0) │ │ │ │ +2B711 GID Size 04 (4) │ │ │ │ +2B712 GID 00000000 (0) │ │ │ │ +2B716 PAYLOAD │ │ │ │ + │ │ │ │ +2BDB9 LOCAL HEADER #18 04034B50 (67324752) │ │ │ │ +2BDBD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +2BDBE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +2BDBF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +2BDC1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +2BDC3 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +2BDC7 CRC F5E61E1F (4125498911) │ │ │ │ +2BDCB Compressed Size 00001082 (4226) │ │ │ │ +2BDCF Uncompressed Size 00004C00 (19456) │ │ │ │ +2BDD3 Filename Length 001B (27) │ │ │ │ +2BDD5 Extra Length 001C (28) │ │ │ │ +2BDD7 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x2BDD7: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +2BDF2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +2BDF4 Length 0009 (9) │ │ │ │ +2BDF6 Flags 03 (3) 'Modification Access' │ │ │ │ +2BDF7 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +2BDFB Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +2BDFF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +2BE01 Length 000B (11) │ │ │ │ +2BE03 Version 01 (1) │ │ │ │ +2BE04 UID Size 04 (4) │ │ │ │ +2BE05 UID 00000000 (0) │ │ │ │ +2BE09 GID Size 04 (4) │ │ │ │ +2BE0A GID 00000000 (0) │ │ │ │ +2BE0E PAYLOAD │ │ │ │ + │ │ │ │ +2CE90 LOCAL HEADER #19 04034B50 (67324752) │ │ │ │ +2CE94 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +2CE95 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +2CE96 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +2CE98 Compression Method 0008 (8) 'Deflated' │ │ │ │ +2CE9A Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +2CE9E CRC 4CF55DF8 (1291148792) │ │ │ │ +2CEA2 Compressed Size 000033AB (13227) │ │ │ │ +2CEA6 Uncompressed Size 0000BC95 (48277) │ │ │ │ +2CEAA Filename Length 001D (29) │ │ │ │ +2CEAC Extra Length 001C (28) │ │ │ │ +2CEAE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x2CEAE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +2CECB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +2CECD Length 0009 (9) │ │ │ │ +2CECF Flags 03 (3) 'Modification Access' │ │ │ │ +2CED0 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +2CED4 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +2CED8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +2CEDA Length 000B (11) │ │ │ │ +2CEDC Version 01 (1) │ │ │ │ +2CEDD UID Size 04 (4) │ │ │ │ +2CEDE UID 00000000 (0) │ │ │ │ +2CEE2 GID Size 04 (4) │ │ │ │ +2CEE3 GID 00000000 (0) │ │ │ │ +2CEE7 PAYLOAD │ │ │ │ + │ │ │ │ +30292 LOCAL HEADER #20 04034B50 (67324752) │ │ │ │ +30296 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +30297 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +30298 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3029A Compression Method 0008 (8) 'Deflated' │ │ │ │ +3029C Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +302A0 CRC 797D5C21 (2038258721) │ │ │ │ +302A4 Compressed Size 00000D6A (3434) │ │ │ │ +302A8 Uncompressed Size 0000388E (14478) │ │ │ │ +302AC Filename Length 001D (29) │ │ │ │ +302AE Extra Length 001C (28) │ │ │ │ +302B0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x302B0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +302CD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +302CF Length 0009 (9) │ │ │ │ +302D1 Flags 03 (3) 'Modification Access' │ │ │ │ +302D2 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +302D6 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +302DA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +302DC Length 000B (11) │ │ │ │ +302DE Version 01 (1) │ │ │ │ +302DF UID Size 04 (4) │ │ │ │ +302E0 UID 00000000 (0) │ │ │ │ +302E4 GID Size 04 (4) │ │ │ │ +302E5 GID 00000000 (0) │ │ │ │ +302E9 PAYLOAD │ │ │ │ + │ │ │ │ +31053 LOCAL HEADER #21 04034B50 (67324752) │ │ │ │ +31057 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +31058 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +31059 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3105B Compression Method 0008 (8) 'Deflated' │ │ │ │ +3105D Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +31061 CRC 20784D7F (544755071) │ │ │ │ +31065 Compressed Size 00001C6A (7274) │ │ │ │ +31069 Uncompressed Size 0000C187 (49543) │ │ │ │ +3106D Filename Length 001A (26) │ │ │ │ +3106F Extra Length 001C (28) │ │ │ │ +31071 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x31071: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3108B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3108D Length 0009 (9) │ │ │ │ +3108F Flags 03 (3) 'Modification Access' │ │ │ │ +31090 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +31094 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +31098 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3109A Length 000B (11) │ │ │ │ +3109C Version 01 (1) │ │ │ │ +3109D UID Size 04 (4) │ │ │ │ +3109E UID 00000000 (0) │ │ │ │ +310A2 GID Size 04 (4) │ │ │ │ +310A3 GID 00000000 (0) │ │ │ │ +310A7 PAYLOAD │ │ │ │ + │ │ │ │ +32D11 LOCAL HEADER #22 04034B50 (67324752) │ │ │ │ +32D15 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +32D16 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +32D17 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +32D19 Compression Method 0008 (8) 'Deflated' │ │ │ │ +32D1B Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +32D1F CRC C41B95CB (3290142155) │ │ │ │ +32D23 Compressed Size 000003A4 (932) │ │ │ │ +32D27 Uncompressed Size 0000088F (2191) │ │ │ │ +32D2B Filename Length 0012 (18) │ │ │ │ +32D2D Extra Length 001C (28) │ │ │ │ +32D2F Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x32D2F: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +32D41 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +32D43 Length 0009 (9) │ │ │ │ +32D45 Flags 03 (3) 'Modification Access' │ │ │ │ +32D46 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +32D4A Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +32D4E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +32D50 Length 000B (11) │ │ │ │ +32D52 Version 01 (1) │ │ │ │ +32D53 UID Size 04 (4) │ │ │ │ +32D54 UID 00000000 (0) │ │ │ │ +32D58 GID Size 04 (4) │ │ │ │ +32D59 GID 00000000 (0) │ │ │ │ +32D5D PAYLOAD │ │ │ │ + │ │ │ │ +33101 LOCAL HEADER #23 04034B50 (67324752) │ │ │ │ +33105 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +33106 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +33107 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +33109 Compression Method 0008 (8) 'Deflated' │ │ │ │ +3310B Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +3310F CRC 40B68220 (1085702688) │ │ │ │ +33113 Compressed Size 000001D4 (468) │ │ │ │ +33117 Uncompressed Size 00000312 (786) │ │ │ │ +3311B Filename Length 0020 (32) │ │ │ │ +3311D Extra Length 001C (28) │ │ │ │ +3311F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3311F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3313F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +33141 Length 0009 (9) │ │ │ │ +33143 Flags 03 (3) 'Modification Access' │ │ │ │ +33144 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +33148 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +3314C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3314E Length 000B (11) │ │ │ │ +33150 Version 01 (1) │ │ │ │ +33151 UID Size 04 (4) │ │ │ │ +33152 UID 00000000 (0) │ │ │ │ +33156 GID Size 04 (4) │ │ │ │ +33157 GID 00000000 (0) │ │ │ │ +3315B PAYLOAD │ │ │ │ + │ │ │ │ +3332F LOCAL HEADER #24 04034B50 (67324752) │ │ │ │ +33333 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +33334 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +33335 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +33337 Compression Method 0008 (8) 'Deflated' │ │ │ │ +33339 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +3333D CRC DA6644FF (3664135423) │ │ │ │ +33341 Compressed Size 000017AA (6058) │ │ │ │ +33345 Uncompressed Size 00009D19 (40217) │ │ │ │ +33349 Filename Length 001B (27) │ │ │ │ +3334B Extra Length 001C (28) │ │ │ │ +3334D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3334D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +33368 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3336A Length 0009 (9) │ │ │ │ +3336C Flags 03 (3) 'Modification Access' │ │ │ │ +3336D Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +33371 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +33375 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +33377 Length 000B (11) │ │ │ │ +33379 Version 01 (1) │ │ │ │ +3337A UID Size 04 (4) │ │ │ │ +3337B UID 00000000 (0) │ │ │ │ +3337F GID Size 04 (4) │ │ │ │ +33380 GID 00000000 (0) │ │ │ │ +33384 PAYLOAD │ │ │ │ + │ │ │ │ +34B2E LOCAL HEADER #25 04034B50 (67324752) │ │ │ │ +34B32 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +34B33 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +34B34 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +34B36 Compression Method 0008 (8) 'Deflated' │ │ │ │ +34B38 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +34B3C CRC 8B976C32 (2341956658) │ │ │ │ +34B40 Compressed Size 00001374 (4980) │ │ │ │ +34B44 Uncompressed Size 00003B67 (15207) │ │ │ │ +34B48 Filename Length 0015 (21) │ │ │ │ +34B4A Extra Length 001C (28) │ │ │ │ +34B4C Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x34B4C: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +34B61 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +34B63 Length 0009 (9) │ │ │ │ +34B65 Flags 03 (3) 'Modification Access' │ │ │ │ +34B66 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +34B6A Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +34B6E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +34B70 Length 000B (11) │ │ │ │ +34B72 Version 01 (1) │ │ │ │ +34B73 UID Size 04 (4) │ │ │ │ +34B74 UID 00000000 (0) │ │ │ │ +34B78 GID Size 04 (4) │ │ │ │ +34B79 GID 00000000 (0) │ │ │ │ +34B7D PAYLOAD │ │ │ │ + │ │ │ │ +35EF1 LOCAL HEADER #26 04034B50 (67324752) │ │ │ │ +35EF5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +35EF6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +35EF7 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +35EF9 Compression Method 0008 (8) 'Deflated' │ │ │ │ +35EFB Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +35EFF CRC 2ACCA91C (718055708) │ │ │ │ +35F03 Compressed Size 00000AD3 (2771) │ │ │ │ +35F07 Uncompressed Size 00002136 (8502) │ │ │ │ +35F0B Filename Length 0011 (17) │ │ │ │ +35F0D Extra Length 001C (28) │ │ │ │ +35F0F Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x35F0F: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +35F20 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +35F22 Length 0009 (9) │ │ │ │ +35F24 Flags 03 (3) 'Modification Access' │ │ │ │ +35F25 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +35F29 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +35F2D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +35F2F Length 000B (11) │ │ │ │ +35F31 Version 01 (1) │ │ │ │ +35F32 UID Size 04 (4) │ │ │ │ +35F33 UID 00000000 (0) │ │ │ │ +35F37 GID Size 04 (4) │ │ │ │ +35F38 GID 00000000 (0) │ │ │ │ +35F3C PAYLOAD │ │ │ │ + │ │ │ │ +36A0F LOCAL HEADER #27 04034B50 (67324752) │ │ │ │ +36A13 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +36A14 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +36A15 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +36A17 Compression Method 0008 (8) 'Deflated' │ │ │ │ +36A19 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +36A1D CRC 323952E0 (842617568) │ │ │ │ +36A21 Compressed Size 000003FE (1022) │ │ │ │ +36A25 Uncompressed Size 00000F0D (3853) │ │ │ │ +36A29 Filename Length 0014 (20) │ │ │ │ +36A2B Extra Length 001C (28) │ │ │ │ +36A2D Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x36A2D: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +36A41 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +36A43 Length 0009 (9) │ │ │ │ +36A45 Flags 03 (3) 'Modification Access' │ │ │ │ +36A46 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +36A4A Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +36A4E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +36A50 Length 000B (11) │ │ │ │ +36A52 Version 01 (1) │ │ │ │ +36A53 UID Size 04 (4) │ │ │ │ +36A54 UID 00000000 (0) │ │ │ │ +36A58 GID Size 04 (4) │ │ │ │ +36A59 GID 00000000 (0) │ │ │ │ +36A5D PAYLOAD │ │ │ │ + │ │ │ │ +36E5B LOCAL HEADER #28 04034B50 (67324752) │ │ │ │ +36E5F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +36E60 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +36E61 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +36E63 Compression Method 0008 (8) 'Deflated' │ │ │ │ +36E65 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +36E69 CRC F2ED2F43 (4075630403) │ │ │ │ +36E6D Compressed Size 00001263 (4707) │ │ │ │ +36E71 Uncompressed Size 0000346A (13418) │ │ │ │ +36E75 Filename Length 0014 (20) │ │ │ │ +36E77 Extra Length 001C (28) │ │ │ │ +36E79 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x36E79: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +36E8D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +36E8F Length 0009 (9) │ │ │ │ +36E91 Flags 03 (3) 'Modification Access' │ │ │ │ +36E92 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +36E96 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +36E9A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +36E9C Length 000B (11) │ │ │ │ +36E9E Version 01 (1) │ │ │ │ +36E9F UID Size 04 (4) │ │ │ │ +36EA0 UID 00000000 (0) │ │ │ │ +36EA4 GID Size 04 (4) │ │ │ │ +36EA5 GID 00000000 (0) │ │ │ │ +36EA9 PAYLOAD │ │ │ │ + │ │ │ │ +3810C LOCAL HEADER #29 04034B50 (67324752) │ │ │ │ +38110 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +38111 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +38112 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +38114 Compression Method 0008 (8) 'Deflated' │ │ │ │ +38116 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +3811A CRC AA8B7F74 (2861268852) │ │ │ │ +3811E Compressed Size 00000AD1 (2769) │ │ │ │ +38122 Uncompressed Size 00002300 (8960) │ │ │ │ +38126 Filename Length 001B (27) │ │ │ │ +38128 Extra Length 001C (28) │ │ │ │ +3812A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3812A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +38145 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +38147 Length 0009 (9) │ │ │ │ +38149 Flags 03 (3) 'Modification Access' │ │ │ │ +3814A Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +3814E Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +38152 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +38154 Length 000B (11) │ │ │ │ +38156 Version 01 (1) │ │ │ │ +38157 UID Size 04 (4) │ │ │ │ +38158 UID 00000000 (0) │ │ │ │ +3815C GID Size 04 (4) │ │ │ │ +3815D GID 00000000 (0) │ │ │ │ +38161 PAYLOAD │ │ │ │ + │ │ │ │ +38C32 LOCAL HEADER #30 04034B50 (67324752) │ │ │ │ +38C36 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +38C37 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +38C38 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +38C3A Compression Method 0008 (8) 'Deflated' │ │ │ │ +38C3C Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +38C40 CRC AB095EFA (2869518074) │ │ │ │ +38C44 Compressed Size 00000A8E (2702) │ │ │ │ +38C48 Uncompressed Size 0000237B (9083) │ │ │ │ +38C4C Filename Length 0013 (19) │ │ │ │ +38C4E Extra Length 001C (28) │ │ │ │ +38C50 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x38C50: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +38C63 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +38C65 Length 0009 (9) │ │ │ │ +38C67 Flags 03 (3) 'Modification Access' │ │ │ │ +38C68 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +38C6C Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +38C70 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +38C72 Length 000B (11) │ │ │ │ +38C74 Version 01 (1) │ │ │ │ +38C75 UID Size 04 (4) │ │ │ │ +38C76 UID 00000000 (0) │ │ │ │ +38C7A GID Size 04 (4) │ │ │ │ +38C7B GID 00000000 (0) │ │ │ │ +38C7F PAYLOAD │ │ │ │ + │ │ │ │ +3970D LOCAL HEADER #31 04034B50 (67324752) │ │ │ │ +39711 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +39712 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +39713 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +39715 Compression Method 0008 (8) 'Deflated' │ │ │ │ +39717 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +3971B CRC DFB4FE0C (3753180684) │ │ │ │ +3971F Compressed Size 000010E7 (4327) │ │ │ │ +39723 Uncompressed Size 00005611 (22033) │ │ │ │ +39727 Filename Length 000F (15) │ │ │ │ +39729 Extra Length 001C (28) │ │ │ │ +3972B Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3972B: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3973A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3973C Length 0009 (9) │ │ │ │ +3973E Flags 03 (3) 'Modification Access' │ │ │ │ +3973F Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +39743 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +39747 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +39749 Length 000B (11) │ │ │ │ +3974B Version 01 (1) │ │ │ │ +3974C UID Size 04 (4) │ │ │ │ +3974D UID 00000000 (0) │ │ │ │ +39751 GID Size 04 (4) │ │ │ │ +39752 GID 00000000 (0) │ │ │ │ +39756 PAYLOAD │ │ │ │ + │ │ │ │ +3A83D LOCAL HEADER #32 04034B50 (67324752) │ │ │ │ +3A841 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3A842 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3A843 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3A845 Compression Method 0008 (8) 'Deflated' │ │ │ │ +3A847 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +3A84B CRC C70CE575 (3339511157) │ │ │ │ +3A84F Compressed Size 0000066B (1643) │ │ │ │ +3A853 Uncompressed Size 000018E0 (6368) │ │ │ │ +3A857 Filename Length 000F (15) │ │ │ │ +3A859 Extra Length 001C (28) │ │ │ │ +3A85B Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3A85B: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3A86A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3A86C Length 0009 (9) │ │ │ │ +3A86E Flags 03 (3) 'Modification Access' │ │ │ │ +3A86F Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +3A873 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +3A877 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3A879 Length 000B (11) │ │ │ │ +3A87B Version 01 (1) │ │ │ │ +3A87C UID Size 04 (4) │ │ │ │ +3A87D UID 00000000 (0) │ │ │ │ +3A881 GID Size 04 (4) │ │ │ │ +3A882 GID 00000000 (0) │ │ │ │ +3A886 PAYLOAD │ │ │ │ + │ │ │ │ +3AEF1 LOCAL HEADER #33 04034B50 (67324752) │ │ │ │ +3AEF5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3AEF6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3AEF7 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3AEF9 Compression Method 0008 (8) 'Deflated' │ │ │ │ +3AEFB Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +3AEFF CRC 1194DF58 (294969176) │ │ │ │ +3AF03 Compressed Size 00001A46 (6726) │ │ │ │ +3AF07 Uncompressed Size 000064F3 (25843) │ │ │ │ +3AF0B Filename Length 0013 (19) │ │ │ │ +3AF0D Extra Length 001C (28) │ │ │ │ +3AF0F Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3AF0F: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3AF22 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3AF24 Length 0009 (9) │ │ │ │ +3AF26 Flags 03 (3) 'Modification Access' │ │ │ │ +3AF27 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +3AF2B Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +3AF2F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3AF31 Length 000B (11) │ │ │ │ +3AF33 Version 01 (1) │ │ │ │ +3AF34 UID Size 04 (4) │ │ │ │ +3AF35 UID 00000000 (0) │ │ │ │ +3AF39 GID Size 04 (4) │ │ │ │ +3AF3A GID 00000000 (0) │ │ │ │ +3AF3E PAYLOAD │ │ │ │ + │ │ │ │ +3C984 LOCAL HEADER #34 04034B50 (67324752) │ │ │ │ +3C988 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3C989 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3C98A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3C98C Compression Method 0008 (8) 'Deflated' │ │ │ │ +3C98E Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +3C992 CRC BA8C2020 (3129745440) │ │ │ │ +3C996 Compressed Size 000009A7 (2471) │ │ │ │ +3C99A Uncompressed Size 00001B65 (7013) │ │ │ │ +3C99E Filename Length 0010 (16) │ │ │ │ +3C9A0 Extra Length 001C (28) │ │ │ │ +3C9A2 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3C9A2: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3C9B2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3C9B4 Length 0009 (9) │ │ │ │ +3C9B6 Flags 03 (3) 'Modification Access' │ │ │ │ +3C9B7 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +3C9BB Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +3C9BF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3C9C1 Length 000B (11) │ │ │ │ +3C9C3 Version 01 (1) │ │ │ │ +3C9C4 UID Size 04 (4) │ │ │ │ +3C9C5 UID 00000000 (0) │ │ │ │ +3C9C9 GID Size 04 (4) │ │ │ │ +3C9CA GID 00000000 (0) │ │ │ │ +3C9CE PAYLOAD │ │ │ │ + │ │ │ │ +3D375 LOCAL HEADER #35 04034B50 (67324752) │ │ │ │ +3D379 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3D37A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3D37B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3D37D Compression Method 0008 (8) 'Deflated' │ │ │ │ +3D37F Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +3D383 CRC 61869F50 (1636212560) │ │ │ │ +3D387 Compressed Size 000006B9 (1721) │ │ │ │ +3D38B Uncompressed Size 00001566 (5478) │ │ │ │ +3D38F Filename Length 0012 (18) │ │ │ │ +3D391 Extra Length 001C (28) │ │ │ │ +3D393 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3D393: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3D3A5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3D3A7 Length 0009 (9) │ │ │ │ +3D3A9 Flags 03 (3) 'Modification Access' │ │ │ │ +3D3AA Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +3D3AE Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +3D3B2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3D3B4 Length 000B (11) │ │ │ │ +3D3B6 Version 01 (1) │ │ │ │ +3D3B7 UID Size 04 (4) │ │ │ │ +3D3B8 UID 00000000 (0) │ │ │ │ +3D3BC GID Size 04 (4) │ │ │ │ +3D3BD GID 00000000 (0) │ │ │ │ +3D3C1 PAYLOAD │ │ │ │ + │ │ │ │ +3DA7A LOCAL HEADER #36 04034B50 (67324752) │ │ │ │ +3DA7E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3DA7F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3DA80 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3DA82 Compression Method 0008 (8) 'Deflated' │ │ │ │ +3DA84 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +3DA88 CRC D4742EBB (3564383931) │ │ │ │ +3DA8C Compressed Size 00002A14 (10772) │ │ │ │ +3DA90 Uncompressed Size 0000B1DD (45533) │ │ │ │ +3DA94 Filename Length 0010 (16) │ │ │ │ +3DA96 Extra Length 001C (28) │ │ │ │ +3DA98 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3DA98: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3DAA8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3DAAA Length 0009 (9) │ │ │ │ +3DAAC Flags 03 (3) 'Modification Access' │ │ │ │ +3DAAD Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +3DAB1 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +3DAB5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3DAB7 Length 000B (11) │ │ │ │ +3DAB9 Version 01 (1) │ │ │ │ +3DABA UID Size 04 (4) │ │ │ │ +3DABB UID 00000000 (0) │ │ │ │ +3DABF GID Size 04 (4) │ │ │ │ +3DAC0 GID 00000000 (0) │ │ │ │ +3DAC4 PAYLOAD │ │ │ │ + │ │ │ │ +404D8 LOCAL HEADER #37 04034B50 (67324752) │ │ │ │ +404DC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +404DD Extract OS 00 (0) 'MS-DOS' │ │ │ │ +404DE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +404E0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +404E2 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +404E6 CRC 9CCBB559 (2630595929) │ │ │ │ +404EA Compressed Size 00001E8A (7818) │ │ │ │ +404EE Uncompressed Size 00009AAB (39595) │ │ │ │ +404F2 Filename Length 0012 (18) │ │ │ │ +404F4 Extra Length 001C (28) │ │ │ │ +404F6 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x404F6: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +40508 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +4050A Length 0009 (9) │ │ │ │ +4050C Flags 03 (3) 'Modification Access' │ │ │ │ +4050D Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +40511 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +40515 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +40517 Length 000B (11) │ │ │ │ +40519 Version 01 (1) │ │ │ │ +4051A UID Size 04 (4) │ │ │ │ +4051B UID 00000000 (0) │ │ │ │ +4051F GID Size 04 (4) │ │ │ │ +40520 GID 00000000 (0) │ │ │ │ +40524 PAYLOAD │ │ │ │ + │ │ │ │ +423AE LOCAL HEADER #38 04034B50 (67324752) │ │ │ │ +423B2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +423B3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +423B4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +423B6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +423B8 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +423BC CRC 2CAA1116 (749342998) │ │ │ │ +423C0 Compressed Size 0000147D (5245) │ │ │ │ +423C4 Uncompressed Size 00007AD0 (31440) │ │ │ │ +423C8 Filename Length 0018 (24) │ │ │ │ +423CA Extra Length 001C (28) │ │ │ │ +423CC Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x423CC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +423E4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +423E6 Length 0009 (9) │ │ │ │ +423E8 Flags 03 (3) 'Modification Access' │ │ │ │ +423E9 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +423ED Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +423F1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +423F3 Length 000B (11) │ │ │ │ +423F5 Version 01 (1) │ │ │ │ +423F6 UID Size 04 (4) │ │ │ │ +423F7 UID 00000000 (0) │ │ │ │ +423FB GID Size 04 (4) │ │ │ │ +423FC GID 00000000 (0) │ │ │ │ +42400 PAYLOAD │ │ │ │ + │ │ │ │ +4387D LOCAL HEADER #39 04034B50 (67324752) │ │ │ │ +43881 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +43882 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +43883 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +43885 Compression Method 0008 (8) 'Deflated' │ │ │ │ +43887 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +4388B CRC B893F0A2 (3096703138) │ │ │ │ +4388F Compressed Size 000018D7 (6359) │ │ │ │ +43893 Uncompressed Size 0000A83A (43066) │ │ │ │ +43897 Filename Length 001F (31) │ │ │ │ +43899 Extra Length 001C (28) │ │ │ │ +4389B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4389B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +438BA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +438BC Length 0009 (9) │ │ │ │ +438BE Flags 03 (3) 'Modification Access' │ │ │ │ +438BF Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +438C3 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +438C7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +438C9 Length 000B (11) │ │ │ │ +438CB Version 01 (1) │ │ │ │ +438CC UID Size 04 (4) │ │ │ │ +438CD UID 00000000 (0) │ │ │ │ +438D1 GID Size 04 (4) │ │ │ │ +438D2 GID 00000000 (0) │ │ │ │ +438D6 PAYLOAD │ │ │ │ + │ │ │ │ +451AD LOCAL HEADER #40 04034B50 (67324752) │ │ │ │ +451B1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +451B2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +451B3 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +451B5 Compression Method 0008 (8) 'Deflated' │ │ │ │ +451B7 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +451BB CRC 8E4EBF9E (2387525534) │ │ │ │ +451BF Compressed Size 000003F8 (1016) │ │ │ │ +451C3 Uncompressed Size 000008A4 (2212) │ │ │ │ +451C7 Filename Length 001E (30) │ │ │ │ +451C9 Extra Length 001C (28) │ │ │ │ +451CB Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x451CB: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +451E9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +451EB Length 0009 (9) │ │ │ │ +451ED Flags 03 (3) 'Modification Access' │ │ │ │ +451EE Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +451F2 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +451F6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +451F8 Length 000B (11) │ │ │ │ +451FA Version 01 (1) │ │ │ │ +451FB UID Size 04 (4) │ │ │ │ +451FC UID 00000000 (0) │ │ │ │ +45200 GID Size 04 (4) │ │ │ │ +45201 GID 00000000 (0) │ │ │ │ +45205 PAYLOAD │ │ │ │ + │ │ │ │ +455FD LOCAL HEADER #41 04034B50 (67324752) │ │ │ │ +45601 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +45602 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +45603 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +45605 Compression Method 0008 (8) 'Deflated' │ │ │ │ +45607 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +4560B CRC D9FD2FF3 (3657248755) │ │ │ │ +4560F Compressed Size 00004296 (17046) │ │ │ │ +45613 Uncompressed Size 0000D8E8 (55528) │ │ │ │ +45617 Filename Length 0013 (19) │ │ │ │ +45619 Extra Length 001C (28) │ │ │ │ +4561B Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4561B: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +4562E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +45630 Length 0009 (9) │ │ │ │ +45632 Flags 03 (3) 'Modification Access' │ │ │ │ +45633 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +45637 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +4563B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +4563D Length 000B (11) │ │ │ │ +4563F Version 01 (1) │ │ │ │ +45640 UID Size 04 (4) │ │ │ │ +45641 UID 00000000 (0) │ │ │ │ +45645 GID Size 04 (4) │ │ │ │ +45646 GID 00000000 (0) │ │ │ │ +4564A PAYLOAD │ │ │ │ + │ │ │ │ +498E0 LOCAL HEADER #42 04034B50 (67324752) │ │ │ │ +498E4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +498E5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +498E6 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +498E8 Compression Method 0008 (8) 'Deflated' │ │ │ │ +498EA Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +498EE CRC 33D65333 (869684019) │ │ │ │ +498F2 Compressed Size 000026C4 (9924) │ │ │ │ +498F6 Uncompressed Size 00006E46 (28230) │ │ │ │ +498FA Filename Length 0019 (25) │ │ │ │ +498FC Extra Length 001C (28) │ │ │ │ +498FE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x498FE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +49917 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +49919 Length 0009 (9) │ │ │ │ +4991B Flags 03 (3) 'Modification Access' │ │ │ │ +4991C Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +49920 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +49924 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +49926 Length 000B (11) │ │ │ │ +49928 Version 01 (1) │ │ │ │ +49929 UID Size 04 (4) │ │ │ │ +4992A UID 00000000 (0) │ │ │ │ +4992E GID Size 04 (4) │ │ │ │ +4992F GID 00000000 (0) │ │ │ │ +49933 PAYLOAD │ │ │ │ + │ │ │ │ +4BFF7 LOCAL HEADER #43 04034B50 (67324752) │ │ │ │ +4BFFB Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4BFFC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4BFFD General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4BFFF Compression Method 0008 (8) 'Deflated' │ │ │ │ +4C001 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +4C005 CRC A84F7DF4 (2823781876) │ │ │ │ +4C009 Compressed Size 0000273B (10043) │ │ │ │ +4C00D Uncompressed Size 00008B84 (35716) │ │ │ │ +4C011 Filename Length 0019 (25) │ │ │ │ +4C013 Extra Length 001C (28) │ │ │ │ +4C015 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4C015: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +4C02E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +4C030 Length 0009 (9) │ │ │ │ +4C032 Flags 03 (3) 'Modification Access' │ │ │ │ +4C033 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +4C037 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +4C03B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +4C03D Length 000B (11) │ │ │ │ +4C03F Version 01 (1) │ │ │ │ +4C040 UID Size 04 (4) │ │ │ │ +4C041 UID 00000000 (0) │ │ │ │ +4C045 GID Size 04 (4) │ │ │ │ +4C046 GID 00000000 (0) │ │ │ │ +4C04A PAYLOAD │ │ │ │ + │ │ │ │ +4E785 LOCAL HEADER #44 04034B50 (67324752) │ │ │ │ +4E789 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4E78A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4E78B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4E78D Compression Method 0008 (8) 'Deflated' │ │ │ │ +4E78F Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +4E793 CRC 3C47A4AF (1011328175) │ │ │ │ +4E797 Compressed Size 00000CF2 (3314) │ │ │ │ +4E79B Uncompressed Size 0000517B (20859) │ │ │ │ +4E79F Filename Length 0021 (33) │ │ │ │ +4E7A1 Extra Length 001C (28) │ │ │ │ +4E7A3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4E7A3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +4E7C4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +4E7C6 Length 0009 (9) │ │ │ │ +4E7C8 Flags 03 (3) 'Modification Access' │ │ │ │ +4E7C9 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +4E7CD Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +4E7D1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +4E7D3 Length 000B (11) │ │ │ │ +4E7D5 Version 01 (1) │ │ │ │ +4E7D6 UID Size 04 (4) │ │ │ │ +4E7D7 UID 00000000 (0) │ │ │ │ +4E7DB GID Size 04 (4) │ │ │ │ +4E7DC GID 00000000 (0) │ │ │ │ +4E7E0 PAYLOAD │ │ │ │ + │ │ │ │ +4F4D2 LOCAL HEADER #45 04034B50 (67324752) │ │ │ │ +4F4D6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4F4D7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4F4D8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4F4DA Compression Method 0008 (8) 'Deflated' │ │ │ │ +4F4DC Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +4F4E0 CRC 4527F9F0 (1160247792) │ │ │ │ +4F4E4 Compressed Size 00000469 (1129) │ │ │ │ +4F4E8 Uncompressed Size 00000932 (2354) │ │ │ │ +4F4EC Filename Length 001B (27) │ │ │ │ +4F4EE Extra Length 001C (28) │ │ │ │ +4F4F0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4F4F0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +4F50B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +4F50D Length 0009 (9) │ │ │ │ +4F50F Flags 03 (3) 'Modification Access' │ │ │ │ +4F510 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +4F514 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +4F518 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +4F51A Length 000B (11) │ │ │ │ +4F51C Version 01 (1) │ │ │ │ +4F51D UID Size 04 (4) │ │ │ │ +4F51E UID 00000000 (0) │ │ │ │ +4F522 GID Size 04 (4) │ │ │ │ +4F523 GID 00000000 (0) │ │ │ │ +4F527 PAYLOAD │ │ │ │ + │ │ │ │ +4F990 LOCAL HEADER #46 04034B50 (67324752) │ │ │ │ +4F994 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4F995 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4F996 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4F998 Compression Method 0008 (8) 'Deflated' │ │ │ │ +4F99A Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +4F99E CRC A2B6650C (2729862412) │ │ │ │ +4F9A2 Compressed Size 000016ED (5869) │ │ │ │ +4F9A6 Uncompressed Size 00007A6E (31342) │ │ │ │ +4F9AA Filename Length 001F (31) │ │ │ │ +4F9AC Extra Length 001C (28) │ │ │ │ +4F9AE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4F9AE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +4F9CD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +4F9CF Length 0009 (9) │ │ │ │ +4F9D1 Flags 03 (3) 'Modification Access' │ │ │ │ +4F9D2 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +4F9D6 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +4F9DA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +4F9DC Length 000B (11) │ │ │ │ +4F9DE Version 01 (1) │ │ │ │ +4F9DF UID Size 04 (4) │ │ │ │ +4F9E0 UID 00000000 (0) │ │ │ │ +4F9E4 GID Size 04 (4) │ │ │ │ +4F9E5 GID 00000000 (0) │ │ │ │ +4F9E9 PAYLOAD │ │ │ │ + │ │ │ │ +510D6 LOCAL HEADER #47 04034B50 (67324752) │ │ │ │ +510DA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +510DB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +510DC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +510DE Compression Method 0008 (8) 'Deflated' │ │ │ │ +510E0 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +510E4 CRC 06C4AFCE (113553358) │ │ │ │ +510E8 Compressed Size 00004166 (16742) │ │ │ │ +510EC Uncompressed Size 0001D160 (119136) │ │ │ │ +510F0 Filename Length 0010 (16) │ │ │ │ +510F2 Extra Length 001C (28) │ │ │ │ +510F4 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x510F4: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +51104 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +51106 Length 0009 (9) │ │ │ │ +51108 Flags 03 (3) 'Modification Access' │ │ │ │ +51109 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +5110D Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +51111 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +51113 Length 000B (11) │ │ │ │ +51115 Version 01 (1) │ │ │ │ +51116 UID Size 04 (4) │ │ │ │ +51117 UID 00000000 (0) │ │ │ │ +5111B GID Size 04 (4) │ │ │ │ +5111C GID 00000000 (0) │ │ │ │ +51120 PAYLOAD │ │ │ │ + │ │ │ │ +55286 LOCAL HEADER #48 04034B50 (67324752) │ │ │ │ +5528A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +5528B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +5528C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +5528E Compression Method 0008 (8) 'Deflated' │ │ │ │ +55290 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +55294 CRC 1C486A07 (474507783) │ │ │ │ +55298 Compressed Size 00000A98 (2712) │ │ │ │ +5529C Uncompressed Size 00002106 (8454) │ │ │ │ +552A0 Filename Length 0014 (20) │ │ │ │ +552A2 Extra Length 001C (28) │ │ │ │ +552A4 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x552A4: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +552B8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +552BA Length 0009 (9) │ │ │ │ +552BC Flags 03 (3) 'Modification Access' │ │ │ │ +552BD Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +552C1 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +552C5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +552C7 Length 000B (11) │ │ │ │ +552C9 Version 01 (1) │ │ │ │ +552CA UID Size 04 (4) │ │ │ │ +552CB UID 00000000 (0) │ │ │ │ +552CF GID Size 04 (4) │ │ │ │ +552D0 GID 00000000 (0) │ │ │ │ +552D4 PAYLOAD │ │ │ │ + │ │ │ │ +55D6C LOCAL HEADER #49 04034B50 (67324752) │ │ │ │ +55D70 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +55D71 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +55D72 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +55D74 Compression Method 0008 (8) 'Deflated' │ │ │ │ +55D76 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +55D7A CRC A5D82A4E (2782407246) │ │ │ │ +55D7E Compressed Size 0000AD70 (44400) │ │ │ │ +55D82 Uncompressed Size 0003EB1B (256795) │ │ │ │ +55D86 Filename Length 0017 (23) │ │ │ │ +55D88 Extra Length 001C (28) │ │ │ │ +55D8A Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x55D8A: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +55DA1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +55DA3 Length 0009 (9) │ │ │ │ +55DA5 Flags 03 (3) 'Modification Access' │ │ │ │ +55DA6 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +55DAA Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +55DAE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +55DB0 Length 000B (11) │ │ │ │ +55DB2 Version 01 (1) │ │ │ │ +55DB3 UID Size 04 (4) │ │ │ │ +55DB4 UID 00000000 (0) │ │ │ │ +55DB8 GID Size 04 (4) │ │ │ │ +55DB9 GID 00000000 (0) │ │ │ │ +55DBD PAYLOAD │ │ │ │ + │ │ │ │ +60B2D LOCAL HEADER #50 04034B50 (67324752) │ │ │ │ +60B31 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +60B32 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +60B33 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +60B35 Compression Method 0008 (8) 'Deflated' │ │ │ │ +60B37 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +60B3B CRC 4F9531C5 (1335177669) │ │ │ │ +60B3F Compressed Size 00000462 (1122) │ │ │ │ +60B43 Uncompressed Size 00000DF4 (3572) │ │ │ │ +60B47 Filename Length 0013 (19) │ │ │ │ +60B49 Extra Length 001C (28) │ │ │ │ +60B4B Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x60B4B: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +60B5E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +60B60 Length 0009 (9) │ │ │ │ +60B62 Flags 03 (3) 'Modification Access' │ │ │ │ +60B63 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +60B67 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +60B6B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +60B6D Length 000B (11) │ │ │ │ +60B6F Version 01 (1) │ │ │ │ +60B70 UID Size 04 (4) │ │ │ │ +60B71 UID 00000000 (0) │ │ │ │ +60B75 GID Size 04 (4) │ │ │ │ +60B76 GID 00000000 (0) │ │ │ │ +60B7A PAYLOAD │ │ │ │ + │ │ │ │ +60FDC LOCAL HEADER #51 04034B50 (67324752) │ │ │ │ +60FE0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +60FE1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +60FE2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +60FE4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +60FE6 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +60FEA CRC EEF96960 (4009322848) │ │ │ │ +60FEE Compressed Size 000014D7 (5335) │ │ │ │ +60FF2 Uncompressed Size 00006893 (26771) │ │ │ │ +60FF6 Filename Length 0012 (18) │ │ │ │ +60FF8 Extra Length 001C (28) │ │ │ │ +60FFA Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x60FFA: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6100C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6100E Length 0009 (9) │ │ │ │ +61010 Flags 03 (3) 'Modification Access' │ │ │ │ +61011 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +61015 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +61019 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6101B Length 000B (11) │ │ │ │ +6101D Version 01 (1) │ │ │ │ +6101E UID Size 04 (4) │ │ │ │ +6101F UID 00000000 (0) │ │ │ │ +61023 GID Size 04 (4) │ │ │ │ +61024 GID 00000000 (0) │ │ │ │ +61028 PAYLOAD │ │ │ │ + │ │ │ │ +624FF LOCAL HEADER #52 04034B50 (67324752) │ │ │ │ +62503 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +62504 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +62505 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +62507 Compression Method 0008 (8) 'Deflated' │ │ │ │ +62509 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +6250D CRC 37877832 (931625010) │ │ │ │ +62511 Compressed Size 000011F1 (4593) │ │ │ │ +62515 Uncompressed Size 0000410D (16653) │ │ │ │ +62519 Filename Length 0012 (18) │ │ │ │ +6251B Extra Length 001C (28) │ │ │ │ +6251D Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6251D: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6252F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +62531 Length 0009 (9) │ │ │ │ +62533 Flags 03 (3) 'Modification Access' │ │ │ │ +62534 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +62538 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +6253C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6253E Length 000B (11) │ │ │ │ +62540 Version 01 (1) │ │ │ │ +62541 UID Size 04 (4) │ │ │ │ +62542 UID 00000000 (0) │ │ │ │ +62546 GID Size 04 (4) │ │ │ │ +62547 GID 00000000 (0) │ │ │ │ +6254B PAYLOAD │ │ │ │ + │ │ │ │ +6373C LOCAL HEADER #53 04034B50 (67324752) │ │ │ │ +63740 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +63741 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +63742 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +63744 Compression Method 0008 (8) 'Deflated' │ │ │ │ +63746 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +6374A CRC 1410FCEA (336657642) │ │ │ │ +6374E Compressed Size 000009DB (2523) │ │ │ │ +63752 Uncompressed Size 0000352A (13610) │ │ │ │ +63756 Filename Length 0019 (25) │ │ │ │ +63758 Extra Length 001C (28) │ │ │ │ +6375A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6375A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +63773 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +63775 Length 0009 (9) │ │ │ │ +63777 Flags 03 (3) 'Modification Access' │ │ │ │ +63778 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +6377C Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +63780 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +63782 Length 000B (11) │ │ │ │ +63784 Version 01 (1) │ │ │ │ +63785 UID Size 04 (4) │ │ │ │ +63786 UID 00000000 (0) │ │ │ │ +6378A GID Size 04 (4) │ │ │ │ +6378B GID 00000000 (0) │ │ │ │ +6378F PAYLOAD │ │ │ │ + │ │ │ │ +6416A LOCAL HEADER #54 04034B50 (67324752) │ │ │ │ +6416E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6416F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +64170 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +64172 Compression Method 0008 (8) 'Deflated' │ │ │ │ +64174 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +64178 CRC 64462F48 (1682321224) │ │ │ │ +6417C Compressed Size 00002021 (8225) │ │ │ │ +64180 Uncompressed Size 00010945 (67909) │ │ │ │ +64184 Filename Length 0019 (25) │ │ │ │ +64186 Extra Length 001C (28) │ │ │ │ +64188 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x64188: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +641A1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +641A3 Length 0009 (9) │ │ │ │ +641A5 Flags 03 (3) 'Modification Access' │ │ │ │ +641A6 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +641AA Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +641AE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +641B0 Length 000B (11) │ │ │ │ +641B2 Version 01 (1) │ │ │ │ +641B3 UID Size 04 (4) │ │ │ │ +641B4 UID 00000000 (0) │ │ │ │ +641B8 GID Size 04 (4) │ │ │ │ +641B9 GID 00000000 (0) │ │ │ │ +641BD PAYLOAD │ │ │ │ + │ │ │ │ +661DE LOCAL HEADER #55 04034B50 (67324752) │ │ │ │ +661E2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +661E3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +661E4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +661E6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +661E8 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +661EC CRC D0372DB1 (3493277105) │ │ │ │ +661F0 Compressed Size 0000177F (6015) │ │ │ │ +661F4 Uncompressed Size 0000472D (18221) │ │ │ │ +661F8 Filename Length 0014 (20) │ │ │ │ +661FA Extra Length 001C (28) │ │ │ │ +661FC Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x661FC: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +66210 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +66212 Length 0009 (9) │ │ │ │ +66214 Flags 03 (3) 'Modification Access' │ │ │ │ +66215 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +66219 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +6621D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6621F Length 000B (11) │ │ │ │ +66221 Version 01 (1) │ │ │ │ +66222 UID Size 04 (4) │ │ │ │ +66223 UID 00000000 (0) │ │ │ │ +66227 GID Size 04 (4) │ │ │ │ +66228 GID 00000000 (0) │ │ │ │ +6622C PAYLOAD │ │ │ │ + │ │ │ │ +679AB LOCAL HEADER #56 04034B50 (67324752) │ │ │ │ +679AF Extract Zip Spec 14 (20) '2.0' │ │ │ │ +679B0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +679B1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +679B3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +679B5 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +679B9 CRC FAE30038 (4209180728) │ │ │ │ +679BD Compressed Size 0000040B (1035) │ │ │ │ +679C1 Uncompressed Size 00000826 (2086) │ │ │ │ +679C5 Filename Length 001C (28) │ │ │ │ +679C7 Extra Length 001C (28) │ │ │ │ +679C9 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x679C9: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +679E5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +679E7 Length 0009 (9) │ │ │ │ +679E9 Flags 03 (3) 'Modification Access' │ │ │ │ +679EA Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +679EE Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +679F2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +679F4 Length 000B (11) │ │ │ │ +679F6 Version 01 (1) │ │ │ │ +679F7 UID Size 04 (4) │ │ │ │ +679F8 UID 00000000 (0) │ │ │ │ +679FC GID Size 04 (4) │ │ │ │ +679FD GID 00000000 (0) │ │ │ │ +67A01 PAYLOAD │ │ │ │ + │ │ │ │ +67E0C LOCAL HEADER #57 04034B50 (67324752) │ │ │ │ +67E10 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +67E11 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +67E12 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +67E14 Compression Method 0008 (8) 'Deflated' │ │ │ │ +67E16 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +67E1A CRC E9FBA175 (3925582197) │ │ │ │ +67E1E Compressed Size 0000249B (9371) │ │ │ │ +67E22 Uncompressed Size 0000B5FA (46586) │ │ │ │ +67E26 Filename Length 001F (31) │ │ │ │ +67E28 Extra Length 001C (28) │ │ │ │ +67E2A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x67E2A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +67E49 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +67E4B Length 0009 (9) │ │ │ │ +67E4D Flags 03 (3) 'Modification Access' │ │ │ │ +67E4E Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +67E52 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +67E56 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +67E58 Length 000B (11) │ │ │ │ +67E5A Version 01 (1) │ │ │ │ +67E5B UID Size 04 (4) │ │ │ │ +67E5C UID 00000000 (0) │ │ │ │ +67E60 GID Size 04 (4) │ │ │ │ +67E61 GID 00000000 (0) │ │ │ │ +67E65 PAYLOAD │ │ │ │ + │ │ │ │ +6A300 LOCAL HEADER #58 04034B50 (67324752) │ │ │ │ +6A304 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6A305 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6A306 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6A308 Compression Method 0008 (8) 'Deflated' │ │ │ │ +6A30A Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +6A30E CRC 1AD87814 (450394132) │ │ │ │ +6A312 Compressed Size 00000E80 (3712) │ │ │ │ +6A316 Uncompressed Size 000052DA (21210) │ │ │ │ +6A31A Filename Length 001F (31) │ │ │ │ +6A31C Extra Length 001C (28) │ │ │ │ +6A31E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6A31E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6A33D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6A33F Length 0009 (9) │ │ │ │ +6A341 Flags 03 (3) 'Modification Access' │ │ │ │ +6A342 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +6A346 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +6A34A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6A34C Length 000B (11) │ │ │ │ +6A34E Version 01 (1) │ │ │ │ +6A34F UID Size 04 (4) │ │ │ │ +6A350 UID 00000000 (0) │ │ │ │ +6A354 GID Size 04 (4) │ │ │ │ +6A355 GID 00000000 (0) │ │ │ │ +6A359 PAYLOAD │ │ │ │ + │ │ │ │ +6B1D9 LOCAL HEADER #59 04034B50 (67324752) │ │ │ │ +6B1DD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6B1DE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6B1DF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6B1E1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +6B1E3 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +6B1E7 CRC 4DE022CE (1306534606) │ │ │ │ +6B1EB Compressed Size 00000A45 (2629) │ │ │ │ +6B1EF Uncompressed Size 0000247B (9339) │ │ │ │ +6B1F3 Filename Length 0013 (19) │ │ │ │ +6B1F5 Extra Length 001C (28) │ │ │ │ +6B1F7 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6B1F7: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6B20A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6B20C Length 0009 (9) │ │ │ │ +6B20E Flags 03 (3) 'Modification Access' │ │ │ │ +6B20F Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +6B213 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +6B217 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6B219 Length 000B (11) │ │ │ │ +6B21B Version 01 (1) │ │ │ │ +6B21C UID Size 04 (4) │ │ │ │ +6B21D UID 00000000 (0) │ │ │ │ +6B221 GID Size 04 (4) │ │ │ │ +6B222 GID 00000000 (0) │ │ │ │ +6B226 PAYLOAD │ │ │ │ + │ │ │ │ +6BC6B LOCAL HEADER #60 04034B50 (67324752) │ │ │ │ +6BC6F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6BC70 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6BC71 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6BC73 Compression Method 0008 (8) 'Deflated' │ │ │ │ +6BC75 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +6BC79 CRC 226C1C3B (577510459) │ │ │ │ +6BC7D Compressed Size 0000248D (9357) │ │ │ │ +6BC81 Uncompressed Size 0000B84D (47181) │ │ │ │ +6BC85 Filename Length 0019 (25) │ │ │ │ +6BC87 Extra Length 001C (28) │ │ │ │ +6BC89 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6BC89: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6BCA2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6BCA4 Length 0009 (9) │ │ │ │ +6BCA6 Flags 03 (3) 'Modification Access' │ │ │ │ +6BCA7 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +6BCAB Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +6BCAF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6BCB1 Length 000B (11) │ │ │ │ +6BCB3 Version 01 (1) │ │ │ │ +6BCB4 UID Size 04 (4) │ │ │ │ +6BCB5 UID 00000000 (0) │ │ │ │ +6BCB9 GID Size 04 (4) │ │ │ │ +6BCBA GID 00000000 (0) │ │ │ │ +6BCBE PAYLOAD │ │ │ │ + │ │ │ │ +6E14B LOCAL HEADER #61 04034B50 (67324752) │ │ │ │ +6E14F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6E150 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6E151 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6E153 Compression Method 0008 (8) 'Deflated' │ │ │ │ +6E155 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +6E159 CRC 687822FC (1752703740) │ │ │ │ +6E15D Compressed Size 00000EF8 (3832) │ │ │ │ +6E161 Uncompressed Size 00003A2D (14893) │ │ │ │ +6E165 Filename Length 0024 (36) │ │ │ │ +6E167 Extra Length 001C (28) │ │ │ │ +6E169 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6E169: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6E18D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6E18F Length 0009 (9) │ │ │ │ +6E191 Flags 03 (3) 'Modification Access' │ │ │ │ +6E192 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +6E196 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +6E19A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6E19C Length 000B (11) │ │ │ │ +6E19E Version 01 (1) │ │ │ │ +6E19F UID Size 04 (4) │ │ │ │ +6E1A0 UID 00000000 (0) │ │ │ │ +6E1A4 GID Size 04 (4) │ │ │ │ +6E1A5 GID 00000000 (0) │ │ │ │ +6E1A9 PAYLOAD │ │ │ │ + │ │ │ │ +6F0A1 LOCAL HEADER #62 04034B50 (67324752) │ │ │ │ +6F0A5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6F0A6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6F0A7 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6F0A9 Compression Method 0008 (8) 'Deflated' │ │ │ │ +6F0AB Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +6F0AF CRC 726CE69D (1919739549) │ │ │ │ +6F0B3 Compressed Size 00001AB9 (6841) │ │ │ │ +6F0B7 Uncompressed Size 00005F39 (24377) │ │ │ │ +6F0BB Filename Length 0017 (23) │ │ │ │ +6F0BD Extra Length 001C (28) │ │ │ │ +6F0BF Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6F0BF: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6F0D6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6F0D8 Length 0009 (9) │ │ │ │ +6F0DA Flags 03 (3) 'Modification Access' │ │ │ │ +6F0DB Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +6F0DF Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +6F0E3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6F0E5 Length 000B (11) │ │ │ │ +6F0E7 Version 01 (1) │ │ │ │ +6F0E8 UID Size 04 (4) │ │ │ │ +6F0E9 UID 00000000 (0) │ │ │ │ +6F0ED GID Size 04 (4) │ │ │ │ +6F0EE GID 00000000 (0) │ │ │ │ +6F0F2 PAYLOAD │ │ │ │ + │ │ │ │ +70BAB LOCAL HEADER #63 04034B50 (67324752) │ │ │ │ +70BAF Extract Zip Spec 14 (20) '2.0' │ │ │ │ +70BB0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +70BB1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +70BB3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +70BB5 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +70BB9 CRC 3DD8544C (1037587532) │ │ │ │ +70BBD Compressed Size 00000ED1 (3793) │ │ │ │ +70BC1 Uncompressed Size 000038DE (14558) │ │ │ │ +70BC5 Filename Length 0023 (35) │ │ │ │ +70BC7 Extra Length 001C (28) │ │ │ │ +70BC9 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x70BC9: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +70BEC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +70BEE Length 0009 (9) │ │ │ │ +70BF0 Flags 03 (3) 'Modification Access' │ │ │ │ +70BF1 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +70BF5 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +70BF9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +70BFB Length 000B (11) │ │ │ │ +70BFD Version 01 (1) │ │ │ │ +70BFE UID Size 04 (4) │ │ │ │ +70BFF UID 00000000 (0) │ │ │ │ +70C03 GID Size 04 (4) │ │ │ │ +70C04 GID 00000000 (0) │ │ │ │ +70C08 PAYLOAD │ │ │ │ + │ │ │ │ +71AD9 LOCAL HEADER #64 04034B50 (67324752) │ │ │ │ +71ADD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +71ADE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +71ADF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +71AE1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +71AE3 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +71AE7 CRC 2DB7929F (767005343) │ │ │ │ +71AEB Compressed Size 00000113 (275) │ │ │ │ +71AEF Uncompressed Size 000001F3 (499) │ │ │ │ +71AF3 Filename Length 001B (27) │ │ │ │ +71AF5 Extra Length 001C (28) │ │ │ │ +71AF7 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x71AF7: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +71B12 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +71B14 Length 0009 (9) │ │ │ │ +71B16 Flags 03 (3) 'Modification Access' │ │ │ │ +71B17 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +71B1B Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +71B1F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +71B21 Length 000B (11) │ │ │ │ +71B23 Version 01 (1) │ │ │ │ +71B24 UID Size 04 (4) │ │ │ │ +71B25 UID 00000000 (0) │ │ │ │ +71B29 GID Size 04 (4) │ │ │ │ +71B2A GID 00000000 (0) │ │ │ │ +71B2E PAYLOAD │ │ │ │ + │ │ │ │ +71C41 LOCAL HEADER #65 04034B50 (67324752) │ │ │ │ +71C45 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +71C46 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +71C47 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +71C49 Compression Method 0008 (8) 'Deflated' │ │ │ │ +71C4B Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +71C4F CRC 8C77D705 (2356664069) │ │ │ │ +71C53 Compressed Size 00001892 (6290) │ │ │ │ +71C57 Uncompressed Size 00008FAD (36781) │ │ │ │ +71C5B Filename Length 001D (29) │ │ │ │ +71C5D Extra Length 001C (28) │ │ │ │ +71C5F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x71C5F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +71C7C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +71C7E Length 0009 (9) │ │ │ │ +71C80 Flags 03 (3) 'Modification Access' │ │ │ │ +71C81 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +71C85 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +71C89 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +71C8B Length 000B (11) │ │ │ │ +71C8D Version 01 (1) │ │ │ │ +71C8E UID Size 04 (4) │ │ │ │ +71C8F UID 00000000 (0) │ │ │ │ +71C93 GID Size 04 (4) │ │ │ │ +71C94 GID 00000000 (0) │ │ │ │ +71C98 PAYLOAD │ │ │ │ + │ │ │ │ +7352A LOCAL HEADER #66 04034B50 (67324752) │ │ │ │ +7352E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +7352F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +73530 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +73532 Compression Method 0008 (8) 'Deflated' │ │ │ │ +73534 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +73538 CRC 516C0F5D (1366036317) │ │ │ │ +7353C Compressed Size 0000164A (5706) │ │ │ │ +73540 Uncompressed Size 00003A9C (15004) │ │ │ │ +73544 Filename Length 0015 (21) │ │ │ │ +73546 Extra Length 001C (28) │ │ │ │ +73548 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x73548: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +7355D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +7355F Length 0009 (9) │ │ │ │ +73561 Flags 03 (3) 'Modification Access' │ │ │ │ +73562 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +73566 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +7356A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +7356C Length 000B (11) │ │ │ │ +7356E Version 01 (1) │ │ │ │ +7356F UID Size 04 (4) │ │ │ │ +73570 UID 00000000 (0) │ │ │ │ +73574 GID Size 04 (4) │ │ │ │ +73575 GID 00000000 (0) │ │ │ │ +73579 PAYLOAD │ │ │ │ + │ │ │ │ +74BC3 LOCAL HEADER #67 04034B50 (67324752) │ │ │ │ +74BC7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +74BC8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +74BC9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +74BCB Compression Method 0008 (8) 'Deflated' │ │ │ │ +74BCD Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +74BD1 CRC A1AA00A3 (2712273059) │ │ │ │ +74BD5 Compressed Size 00003B52 (15186) │ │ │ │ +74BD9 Uncompressed Size 00011CC3 (72899) │ │ │ │ +74BDD Filename Length 0016 (22) │ │ │ │ +74BDF Extra Length 001C (28) │ │ │ │ +74BE1 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x74BE1: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +74BF7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +74BF9 Length 0009 (9) │ │ │ │ +74BFB Flags 03 (3) 'Modification Access' │ │ │ │ +74BFC Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +74C00 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +74C04 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +74C06 Length 000B (11) │ │ │ │ +74C08 Version 01 (1) │ │ │ │ +74C09 UID Size 04 (4) │ │ │ │ +74C0A UID 00000000 (0) │ │ │ │ +74C0E GID Size 04 (4) │ │ │ │ +74C0F GID 00000000 (0) │ │ │ │ +74C13 PAYLOAD │ │ │ │ + │ │ │ │ +78765 LOCAL HEADER #68 04034B50 (67324752) │ │ │ │ +78769 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +7876A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +7876B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +7876D Compression Method 0008 (8) 'Deflated' │ │ │ │ +7876F Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +78773 CRC 26977330 (647459632) │ │ │ │ +78777 Compressed Size 00003EC8 (16072) │ │ │ │ +7877B Uncompressed Size 0001C79C (116636) │ │ │ │ +7877F Filename Length 0019 (25) │ │ │ │ +78781 Extra Length 001C (28) │ │ │ │ +78783 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x78783: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +7879C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +7879E Length 0009 (9) │ │ │ │ +787A0 Flags 03 (3) 'Modification Access' │ │ │ │ +787A1 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +787A5 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +787A9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +787AB Length 000B (11) │ │ │ │ +787AD Version 01 (1) │ │ │ │ +787AE UID Size 04 (4) │ │ │ │ +787AF UID 00000000 (0) │ │ │ │ +787B3 GID Size 04 (4) │ │ │ │ +787B4 GID 00000000 (0) │ │ │ │ +787B8 PAYLOAD │ │ │ │ + │ │ │ │ +7C680 LOCAL HEADER #69 04034B50 (67324752) │ │ │ │ +7C684 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +7C685 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +7C686 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +7C688 Compression Method 0008 (8) 'Deflated' │ │ │ │ +7C68A Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +7C68E CRC 510D6570 (1359832432) │ │ │ │ +7C692 Compressed Size 00000838 (2104) │ │ │ │ +7C696 Uncompressed Size 00003384 (13188) │ │ │ │ +7C69A Filename Length 0011 (17) │ │ │ │ +7C69C Extra Length 001C (28) │ │ │ │ +7C69E Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x7C69E: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +7C6AF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +7C6B1 Length 0009 (9) │ │ │ │ +7C6B3 Flags 03 (3) 'Modification Access' │ │ │ │ +7C6B4 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +7C6B8 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +7C6BC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +7C6BE Length 000B (11) │ │ │ │ +7C6C0 Version 01 (1) │ │ │ │ +7C6C1 UID Size 04 (4) │ │ │ │ +7C6C2 UID 00000000 (0) │ │ │ │ +7C6C6 GID Size 04 (4) │ │ │ │ +7C6C7 GID 00000000 (0) │ │ │ │ +7C6CB PAYLOAD │ │ │ │ + │ │ │ │ +7CF03 LOCAL HEADER #70 04034B50 (67324752) │ │ │ │ +7CF07 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +7CF08 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +7CF09 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +7CF0B Compression Method 0008 (8) 'Deflated' │ │ │ │ +7CF0D Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +7CF11 CRC B744432E (3074704174) │ │ │ │ +7CF15 Compressed Size 000051A7 (20903) │ │ │ │ +7CF19 Uncompressed Size 0001FBE0 (130016) │ │ │ │ +7CF1D Filename Length 0015 (21) │ │ │ │ +7CF1F Extra Length 001C (28) │ │ │ │ +7CF21 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x7CF21: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +7CF36 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +7CF38 Length 0009 (9) │ │ │ │ +7CF3A Flags 03 (3) 'Modification Access' │ │ │ │ +7CF3B Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +7CF3F Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +7CF43 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +7CF45 Length 000B (11) │ │ │ │ +7CF47 Version 01 (1) │ │ │ │ +7CF48 UID Size 04 (4) │ │ │ │ +7CF49 UID 00000000 (0) │ │ │ │ +7CF4D GID Size 04 (4) │ │ │ │ +7CF4E GID 00000000 (0) │ │ │ │ +7CF52 PAYLOAD │ │ │ │ + │ │ │ │ +820F9 LOCAL HEADER #71 04034B50 (67324752) │ │ │ │ +820FD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +820FE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +820FF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +82101 Compression Method 0008 (8) 'Deflated' │ │ │ │ +82103 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +82107 CRC 5D0D793D (1561164093) │ │ │ │ +8210B Compressed Size 00001B0A (6922) │ │ │ │ +8210F Uncompressed Size 000081D0 (33232) │ │ │ │ +82113 Filename Length 0019 (25) │ │ │ │ +82115 Extra Length 001C (28) │ │ │ │ +82117 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x82117: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +82130 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +82132 Length 0009 (9) │ │ │ │ +82134 Flags 03 (3) 'Modification Access' │ │ │ │ +82135 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +82139 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +8213D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8213F Length 000B (11) │ │ │ │ +82141 Version 01 (1) │ │ │ │ +82142 UID Size 04 (4) │ │ │ │ +82143 UID 00000000 (0) │ │ │ │ +82147 GID Size 04 (4) │ │ │ │ +82148 GID 00000000 (0) │ │ │ │ +8214C PAYLOAD │ │ │ │ + │ │ │ │ +83C56 LOCAL HEADER #72 04034B50 (67324752) │ │ │ │ +83C5A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +83C5B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +83C5C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +83C5E Compression Method 0008 (8) 'Deflated' │ │ │ │ +83C60 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +83C64 CRC 392C251C (959194396) │ │ │ │ +83C68 Compressed Size 00000D9A (3482) │ │ │ │ +83C6C Uncompressed Size 00002EA0 (11936) │ │ │ │ +83C70 Filename Length 0018 (24) │ │ │ │ +83C72 Extra Length 001C (28) │ │ │ │ +83C74 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x83C74: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +83C8C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +83C8E Length 0009 (9) │ │ │ │ +83C90 Flags 03 (3) 'Modification Access' │ │ │ │ +83C91 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +83C95 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +83C99 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +83C9B Length 000B (11) │ │ │ │ +83C9D Version 01 (1) │ │ │ │ +83C9E UID Size 04 (4) │ │ │ │ +83C9F UID 00000000 (0) │ │ │ │ +83CA3 GID Size 04 (4) │ │ │ │ +83CA4 GID 00000000 (0) │ │ │ │ +83CA8 PAYLOAD │ │ │ │ + │ │ │ │ +84A42 LOCAL HEADER #73 04034B50 (67324752) │ │ │ │ +84A46 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +84A47 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +84A48 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +84A4A Compression Method 0008 (8) 'Deflated' │ │ │ │ +84A4C Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +84A50 CRC 037E3A9E (58604190) │ │ │ │ +84A54 Compressed Size 000001E1 (481) │ │ │ │ +84A58 Uncompressed Size 00000324 (804) │ │ │ │ +84A5C Filename Length 0011 (17) │ │ │ │ +84A5E Extra Length 001C (28) │ │ │ │ +84A60 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x84A60: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +84A71 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +84A73 Length 0009 (9) │ │ │ │ +84A75 Flags 03 (3) 'Modification Access' │ │ │ │ +84A76 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +84A7A Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +84A7E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +84A80 Length 000B (11) │ │ │ │ +84A82 Version 01 (1) │ │ │ │ +84A83 UID Size 04 (4) │ │ │ │ +84A84 UID 00000000 (0) │ │ │ │ +84A88 GID Size 04 (4) │ │ │ │ +84A89 GID 00000000 (0) │ │ │ │ +84A8D PAYLOAD │ │ │ │ + │ │ │ │ +84C6E LOCAL HEADER #74 04034B50 (67324752) │ │ │ │ +84C72 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +84C73 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +84C74 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +84C76 Compression Method 0008 (8) 'Deflated' │ │ │ │ +84C78 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +84C7C CRC 55187787 (1427666823) │ │ │ │ +84C80 Compressed Size 000006C3 (1731) │ │ │ │ +84C84 Uncompressed Size 0000143A (5178) │ │ │ │ +84C88 Filename Length 0019 (25) │ │ │ │ +84C8A Extra Length 001C (28) │ │ │ │ +84C8C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x84C8C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +84CA5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +84CA7 Length 0009 (9) │ │ │ │ +84CA9 Flags 03 (3) 'Modification Access' │ │ │ │ +84CAA Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +84CAE Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +84CB2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +84CB4 Length 000B (11) │ │ │ │ +84CB6 Version 01 (1) │ │ │ │ +84CB7 UID Size 04 (4) │ │ │ │ +84CB8 UID 00000000 (0) │ │ │ │ +84CBC GID Size 04 (4) │ │ │ │ +84CBD GID 00000000 (0) │ │ │ │ +84CC1 PAYLOAD │ │ │ │ + │ │ │ │ +85384 LOCAL HEADER #75 04034B50 (67324752) │ │ │ │ +85388 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +85389 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8538A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8538C Compression Method 0008 (8) 'Deflated' │ │ │ │ +8538E Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +85392 CRC 107EE648 (276751944) │ │ │ │ +85396 Compressed Size 00001EAC (7852) │ │ │ │ +8539A Uncompressed Size 0000CA81 (51841) │ │ │ │ +8539E Filename Length 0018 (24) │ │ │ │ +853A0 Extra Length 001C (28) │ │ │ │ +853A2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x853A2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +853BA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +853BC Length 0009 (9) │ │ │ │ +853BE Flags 03 (3) 'Modification Access' │ │ │ │ +853BF Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +853C3 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +853C7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +853C9 Length 000B (11) │ │ │ │ +853CB Version 01 (1) │ │ │ │ +853CC UID Size 04 (4) │ │ │ │ +853CD UID 00000000 (0) │ │ │ │ +853D1 GID Size 04 (4) │ │ │ │ +853D2 GID 00000000 (0) │ │ │ │ +853D6 PAYLOAD │ │ │ │ + │ │ │ │ +87282 LOCAL HEADER #76 04034B50 (67324752) │ │ │ │ +87286 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +87287 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +87288 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8728A Compression Method 0008 (8) 'Deflated' │ │ │ │ +8728C Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +87290 CRC 45F6BF5A (1173798746) │ │ │ │ +87294 Compressed Size 00001BEB (7147) │ │ │ │ +87298 Uncompressed Size 0000C5EA (50666) │ │ │ │ +8729C Filename Length 0012 (18) │ │ │ │ +8729E Extra Length 001C (28) │ │ │ │ +872A0 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x872A0: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +872B2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +872B4 Length 0009 (9) │ │ │ │ +872B6 Flags 03 (3) 'Modification Access' │ │ │ │ +872B7 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +872BB Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +872BF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +872C1 Length 000B (11) │ │ │ │ +872C3 Version 01 (1) │ │ │ │ +872C4 UID Size 04 (4) │ │ │ │ +872C5 UID 00000000 (0) │ │ │ │ +872C9 GID Size 04 (4) │ │ │ │ +872CA GID 00000000 (0) │ │ │ │ +872CE PAYLOAD │ │ │ │ + │ │ │ │ +88EB9 LOCAL HEADER #77 04034B50 (67324752) │ │ │ │ +88EBD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +88EBE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +88EBF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +88EC1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +88EC3 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +88EC7 CRC 317BA035 (830185525) │ │ │ │ +88ECB Compressed Size 00001E13 (7699) │ │ │ │ +88ECF Uncompressed Size 00008804 (34820) │ │ │ │ +88ED3 Filename Length 0016 (22) │ │ │ │ +88ED5 Extra Length 001C (28) │ │ │ │ +88ED7 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x88ED7: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +88EED Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +88EEF Length 0009 (9) │ │ │ │ +88EF1 Flags 03 (3) 'Modification Access' │ │ │ │ +88EF2 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +88EF6 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +88EFA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +88EFC Length 000B (11) │ │ │ │ +88EFE Version 01 (1) │ │ │ │ +88EFF UID Size 04 (4) │ │ │ │ +88F00 UID 00000000 (0) │ │ │ │ +88F04 GID Size 04 (4) │ │ │ │ +88F05 GID 00000000 (0) │ │ │ │ +88F09 PAYLOAD │ │ │ │ + │ │ │ │ +8AD1C LOCAL HEADER #78 04034B50 (67324752) │ │ │ │ +8AD20 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +8AD21 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8AD22 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8AD24 Compression Method 0008 (8) 'Deflated' │ │ │ │ +8AD26 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +8AD2A CRC B0584996 (2958576022) │ │ │ │ +8AD2E Compressed Size 000029AA (10666) │ │ │ │ +8AD32 Uncompressed Size 0000D050 (53328) │ │ │ │ +8AD36 Filename Length 001A (26) │ │ │ │ +8AD38 Extra Length 001C (28) │ │ │ │ +8AD3A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8AD3A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +8AD54 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +8AD56 Length 0009 (9) │ │ │ │ +8AD58 Flags 03 (3) 'Modification Access' │ │ │ │ +8AD59 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +8AD5D Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +8AD61 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8AD63 Length 000B (11) │ │ │ │ +8AD65 Version 01 (1) │ │ │ │ +8AD66 UID Size 04 (4) │ │ │ │ +8AD67 UID 00000000 (0) │ │ │ │ +8AD6B GID Size 04 (4) │ │ │ │ +8AD6C GID 00000000 (0) │ │ │ │ +8AD70 PAYLOAD │ │ │ │ + │ │ │ │ +8D71A LOCAL HEADER #79 04034B50 (67324752) │ │ │ │ +8D71E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +8D71F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8D720 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8D722 Compression Method 0008 (8) 'Deflated' │ │ │ │ +8D724 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +8D728 CRC 7FA7262B (2141660715) │ │ │ │ +8D72C Compressed Size 000009AD (2477) │ │ │ │ +8D730 Uncompressed Size 00001DB7 (7607) │ │ │ │ +8D734 Filename Length 0018 (24) │ │ │ │ +8D736 Extra Length 001C (28) │ │ │ │ +8D738 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8D738: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +8D750 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +8D752 Length 0009 (9) │ │ │ │ +8D754 Flags 03 (3) 'Modification Access' │ │ │ │ +8D755 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +8D759 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +8D75D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8D75F Length 000B (11) │ │ │ │ +8D761 Version 01 (1) │ │ │ │ +8D762 UID Size 04 (4) │ │ │ │ +8D763 UID 00000000 (0) │ │ │ │ +8D767 GID Size 04 (4) │ │ │ │ +8D768 GID 00000000 (0) │ │ │ │ +8D76C PAYLOAD │ │ │ │ + │ │ │ │ +8E119 LOCAL HEADER #80 04034B50 (67324752) │ │ │ │ +8E11D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +8E11E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8E11F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8E121 Compression Method 0008 (8) 'Deflated' │ │ │ │ +8E123 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +8E127 CRC F5E2129F (4125233823) │ │ │ │ +8E12B Compressed Size 000016BC (5820) │ │ │ │ +8E12F Uncompressed Size 000016CD (5837) │ │ │ │ +8E133 Filename Length 0015 (21) │ │ │ │ +8E135 Extra Length 001C (28) │ │ │ │ +8E137 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8E137: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +8E14C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +8E14E Length 0009 (9) │ │ │ │ +8E150 Flags 03 (3) 'Modification Access' │ │ │ │ +8E151 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +8E155 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +8E159 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8E15B Length 000B (11) │ │ │ │ +8E15D Version 01 (1) │ │ │ │ +8E15E UID Size 04 (4) │ │ │ │ +8E15F UID 00000000 (0) │ │ │ │ +8E163 GID Size 04 (4) │ │ │ │ +8E164 GID 00000000 (0) │ │ │ │ +8E168 PAYLOAD │ │ │ │ + │ │ │ │ +8F824 LOCAL HEADER #81 04034B50 (67324752) │ │ │ │ +8F828 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +8F829 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8F82A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8F82C Compression Method 0008 (8) 'Deflated' │ │ │ │ +8F82E Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +8F832 CRC F5E2129F (4125233823) │ │ │ │ +8F836 Compressed Size 000016BC (5820) │ │ │ │ +8F83A Uncompressed Size 000016CD (5837) │ │ │ │ +8F83E Filename Length 001C (28) │ │ │ │ +8F840 Extra Length 001C (28) │ │ │ │ +8F842 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8F842: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +8F85E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +8F860 Length 0009 (9) │ │ │ │ +8F862 Flags 03 (3) 'Modification Access' │ │ │ │ +8F863 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +8F867 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +8F86B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8F86D Length 000B (11) │ │ │ │ +8F86F Version 01 (1) │ │ │ │ +8F870 UID Size 04 (4) │ │ │ │ +8F871 UID 00000000 (0) │ │ │ │ +8F875 GID Size 04 (4) │ │ │ │ +8F876 GID 00000000 (0) │ │ │ │ +8F87A PAYLOAD │ │ │ │ + │ │ │ │ +90F36 LOCAL HEADER #82 04034B50 (67324752) │ │ │ │ +90F3A Extract Zip Spec 0A (10) '1.0' │ │ │ │ +90F3B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +90F3C General Purpose Flag 0000 (0) │ │ │ │ +90F3E Compression Method 0000 (0) 'Stored' │ │ │ │ +90F40 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +90F44 CRC FC95F24B (4237685323) │ │ │ │ +90F48 Compressed Size 00001B84 (7044) │ │ │ │ +90F4C Uncompressed Size 00001B84 (7044) │ │ │ │ +90F50 Filename Length 0016 (22) │ │ │ │ +90F52 Extra Length 001C (28) │ │ │ │ +90F54 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x90F54: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +90F6A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +90F6C Length 0009 (9) │ │ │ │ +90F6E Flags 03 (3) 'Modification Access' │ │ │ │ +90F6F Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +90F73 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +90F77 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +90F79 Length 000B (11) │ │ │ │ +90F7B Version 01 (1) │ │ │ │ +90F7C UID Size 04 (4) │ │ │ │ +90F7D UID 00000000 (0) │ │ │ │ +90F81 GID Size 04 (4) │ │ │ │ +90F82 GID 00000000 (0) │ │ │ │ +90F86 PAYLOAD │ │ │ │ + │ │ │ │ +92B0A LOCAL HEADER #83 04034B50 (67324752) │ │ │ │ +92B0E Extract Zip Spec 0A (10) '1.0' │ │ │ │ +92B0F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +92B10 General Purpose Flag 0000 (0) │ │ │ │ +92B12 Compression Method 0000 (0) 'Stored' │ │ │ │ +92B14 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +92B18 CRC D0D71F86 (3503759238) │ │ │ │ +92B1C Compressed Size 00000B7B (2939) │ │ │ │ +92B20 Uncompressed Size 00000B7B (2939) │ │ │ │ +92B24 Filename Length 0016 (22) │ │ │ │ +92B26 Extra Length 001C (28) │ │ │ │ +92B28 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x92B28: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +92B3E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +92B40 Length 0009 (9) │ │ │ │ +92B42 Flags 03 (3) 'Modification Access' │ │ │ │ +92B43 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +92B47 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +92B4B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +92B4D Length 000B (11) │ │ │ │ +92B4F Version 01 (1) │ │ │ │ +92B50 UID Size 04 (4) │ │ │ │ +92B51 UID 00000000 (0) │ │ │ │ +92B55 GID Size 04 (4) │ │ │ │ +92B56 GID 00000000 (0) │ │ │ │ +92B5A PAYLOAD │ │ │ │ + │ │ │ │ +936D5 LOCAL HEADER #84 04034B50 (67324752) │ │ │ │ +936D9 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +936DA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +936DB General Purpose Flag 0000 (0) │ │ │ │ +936DD Compression Method 0000 (0) 'Stored' │ │ │ │ +936DF Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +936E3 CRC FFF9C4D2 (4294558930) │ │ │ │ +936E7 Compressed Size 0000138F (5007) │ │ │ │ +936EB Uncompressed Size 0000138F (5007) │ │ │ │ +936EF Filename Length 0016 (22) │ │ │ │ +936F1 Extra Length 001C (28) │ │ │ │ +936F3 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x936F3: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +93709 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9370B Length 0009 (9) │ │ │ │ +9370D Flags 03 (3) 'Modification Access' │ │ │ │ +9370E Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +93712 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +93716 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +93718 Length 000B (11) │ │ │ │ +9371A Version 01 (1) │ │ │ │ +9371B UID Size 04 (4) │ │ │ │ +9371C UID 00000000 (0) │ │ │ │ +93720 GID Size 04 (4) │ │ │ │ +93721 GID 00000000 (0) │ │ │ │ +93725 PAYLOAD │ │ │ │ + │ │ │ │ +94AB4 LOCAL HEADER #85 04034B50 (67324752) │ │ │ │ +94AB8 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +94AB9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +94ABA General Purpose Flag 0000 (0) │ │ │ │ +94ABC Compression Method 0000 (0) 'Stored' │ │ │ │ +94ABE Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +94AC2 CRC A1037E8E (2701360782) │ │ │ │ +94AC6 Compressed Size 0000145E (5214) │ │ │ │ +94ACA Uncompressed Size 0000145E (5214) │ │ │ │ +94ACE Filename Length 0016 (22) │ │ │ │ +94AD0 Extra Length 001C (28) │ │ │ │ +94AD2 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x94AD2: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +94AE8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +94AEA Length 0009 (9) │ │ │ │ +94AEC Flags 03 (3) 'Modification Access' │ │ │ │ +94AED Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +94AF1 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +94AF5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +94AF7 Length 000B (11) │ │ │ │ +94AF9 Version 01 (1) │ │ │ │ +94AFA UID Size 04 (4) │ │ │ │ +94AFB UID 00000000 (0) │ │ │ │ +94AFF GID Size 04 (4) │ │ │ │ +94B00 GID 00000000 (0) │ │ │ │ +94B04 PAYLOAD │ │ │ │ + │ │ │ │ +95F62 LOCAL HEADER #86 04034B50 (67324752) │ │ │ │ +95F66 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +95F67 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +95F68 General Purpose Flag 0000 (0) │ │ │ │ +95F6A Compression Method 0000 (0) 'Stored' │ │ │ │ +95F6C Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +95F70 CRC 5E9E64F1 (1587438833) │ │ │ │ +95F74 Compressed Size 000008EC (2284) │ │ │ │ +95F78 Uncompressed Size 000008EC (2284) │ │ │ │ +95F7C Filename Length 0016 (22) │ │ │ │ +95F7E Extra Length 001C (28) │ │ │ │ +95F80 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x95F80: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +95F96 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +95F98 Length 0009 (9) │ │ │ │ +95F9A Flags 03 (3) 'Modification Access' │ │ │ │ +95F9B Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +95F9F Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +95FA3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +95FA5 Length 000B (11) │ │ │ │ +95FA7 Version 01 (1) │ │ │ │ +95FA8 UID Size 04 (4) │ │ │ │ +95FA9 UID 00000000 (0) │ │ │ │ +95FAD GID Size 04 (4) │ │ │ │ +95FAE GID 00000000 (0) │ │ │ │ +95FB2 PAYLOAD │ │ │ │ + │ │ │ │ +9689E LOCAL HEADER #87 04034B50 (67324752) │ │ │ │ +968A2 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +968A3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +968A4 General Purpose Flag 0000 (0) │ │ │ │ +968A6 Compression Method 0000 (0) 'Stored' │ │ │ │ +968A8 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +968AC CRC 42E340AB (1122189483) │ │ │ │ +968B0 Compressed Size 00001F2E (7982) │ │ │ │ +968B4 Uncompressed Size 00001F2E (7982) │ │ │ │ +968B8 Filename Length 001E (30) │ │ │ │ +968BA Extra Length 001C (28) │ │ │ │ +968BC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x968BC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +968DA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +968DC Length 0009 (9) │ │ │ │ +968DE Flags 03 (3) 'Modification Access' │ │ │ │ +968DF Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +968E3 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +968E7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +968E9 Length 000B (11) │ │ │ │ +968EB Version 01 (1) │ │ │ │ +968EC UID Size 04 (4) │ │ │ │ +968ED UID 00000000 (0) │ │ │ │ +968F1 GID Size 04 (4) │ │ │ │ +968F2 GID 00000000 (0) │ │ │ │ +968F6 PAYLOAD │ │ │ │ + │ │ │ │ +98824 LOCAL HEADER #88 04034B50 (67324752) │ │ │ │ +98828 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +98829 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9882A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9882C Compression Method 0008 (8) 'Deflated' │ │ │ │ +9882E Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +98832 CRC 2D9B7122 (765161762) │ │ │ │ +98836 Compressed Size 00004099 (16537) │ │ │ │ +9883A Uncompressed Size 0001971B (104219) │ │ │ │ +9883E Filename Length 001A (26) │ │ │ │ +98840 Extra Length 001C (28) │ │ │ │ +98842 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x98842: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9885C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9885E Length 0009 (9) │ │ │ │ +98860 Flags 03 (3) 'Modification Access' │ │ │ │ +98861 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +98865 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +98869 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9886B Length 000B (11) │ │ │ │ +9886D Version 01 (1) │ │ │ │ +9886E UID Size 04 (4) │ │ │ │ +9886F UID 00000000 (0) │ │ │ │ +98873 GID Size 04 (4) │ │ │ │ +98874 GID 00000000 (0) │ │ │ │ +98878 PAYLOAD │ │ │ │ + │ │ │ │ +9C911 LOCAL HEADER #89 04034B50 (67324752) │ │ │ │ +9C915 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9C916 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9C917 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9C919 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9C91B Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9C91F CRC 0819CE01 (135908865) │ │ │ │ +9C923 Compressed Size 000029D0 (10704) │ │ │ │ +9C927 Uncompressed Size 0000BB3A (47930) │ │ │ │ +9C92B Filename Length 0018 (24) │ │ │ │ +9C92D Extra Length 001C (28) │ │ │ │ +9C92F Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9C92F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9C947 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9C949 Length 0009 (9) │ │ │ │ +9C94B Flags 03 (3) 'Modification Access' │ │ │ │ +9C94C Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9C950 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9C954 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9C956 Length 000B (11) │ │ │ │ +9C958 Version 01 (1) │ │ │ │ +9C959 UID Size 04 (4) │ │ │ │ +9C95A UID 00000000 (0) │ │ │ │ +9C95E GID Size 04 (4) │ │ │ │ +9C95F GID 00000000 (0) │ │ │ │ +9C963 PAYLOAD │ │ │ │ + │ │ │ │ +9F333 LOCAL HEADER #90 04034B50 (67324752) │ │ │ │ +9F337 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F338 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F339 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F33B Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F33D Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9F341 CRC DCB3B516 (3702764822) │ │ │ │ +9F345 Compressed Size 000000AE (174) │ │ │ │ +9F349 Uncompressed Size 000000FC (252) │ │ │ │ +9F34D Filename Length 0016 (22) │ │ │ │ +9F34F Extra Length 001C (28) │ │ │ │ +9F351 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F351: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F367 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F369 Length 0009 (9) │ │ │ │ +9F36B Flags 03 (3) 'Modification Access' │ │ │ │ +9F36C Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9F370 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9F374 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F376 Length 000B (11) │ │ │ │ +9F378 Version 01 (1) │ │ │ │ +9F379 UID Size 04 (4) │ │ │ │ +9F37A UID 00000000 (0) │ │ │ │ +9F37E GID Size 04 (4) │ │ │ │ +9F37F GID 00000000 (0) │ │ │ │ +9F383 PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ │ │ │ │ -9F425 LOCAL HEADER #91 04034B50 (67324752) │ │ │ │ -9F429 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F42A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F42B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F42D Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F42F Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F433 CRC 58439733 (1480824627) │ │ │ │ -9F437 Compressed Size 00000077 (119) │ │ │ │ -9F43B Uncompressed Size 000000A2 (162) │ │ │ │ -9F43F Filename Length 002D (45) │ │ │ │ -9F441 Extra Length 001C (28) │ │ │ │ -9F443 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F443: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F470 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F472 Length 0009 (9) │ │ │ │ -9F474 Flags 03 (3) 'Modification Access' │ │ │ │ -9F475 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F479 Access Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F47D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F47F Length 000B (11) │ │ │ │ -9F481 Version 01 (1) │ │ │ │ -9F482 UID Size 04 (4) │ │ │ │ -9F483 UID 00000000 (0) │ │ │ │ -9F487 GID Size 04 (4) │ │ │ │ -9F488 GID 00000000 (0) │ │ │ │ -9F48C PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ - │ │ │ │ -9F503 CENTRAL HEADER #1 02014B50 (33639248) │ │ │ │ -9F507 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F508 Created OS 03 (3) 'Unix' │ │ │ │ -9F509 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -9F50A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F50B General Purpose Flag 0000 (0) │ │ │ │ -9F50D Compression Method 0000 (0) 'Stored' │ │ │ │ -9F50F Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F513 CRC 2CAB616F (749429103) │ │ │ │ -9F517 Compressed Size 00000014 (20) │ │ │ │ -9F51B Uncompressed Size 00000014 (20) │ │ │ │ -9F51F Filename Length 0008 (8) │ │ │ │ -9F521 Extra Length 0018 (24) │ │ │ │ -9F523 Comment Length 0000 (0) │ │ │ │ -9F525 Disk Start 0000 (0) │ │ │ │ -9F527 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F529 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F52D Local Header Offset 00000000 (0) │ │ │ │ -9F531 Filename 'XXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F531: Filename 'XXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F539 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F53B Length 0005 (5) │ │ │ │ -9F53D Flags 01 (1) 'Modification' │ │ │ │ -9F53E Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F542 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F544 Length 000B (11) │ │ │ │ -9F546 Version 01 (1) │ │ │ │ -9F547 UID Size 04 (4) │ │ │ │ -9F548 UID 00000000 (0) │ │ │ │ -9F54C GID Size 04 (4) │ │ │ │ -9F54D GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F551 CENTRAL HEADER #2 02014B50 (33639248) │ │ │ │ -9F555 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F556 Created OS 03 (3) 'Unix' │ │ │ │ -9F557 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F558 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F559 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F55B Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F55D Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F561 CRC 7CFCE72A (2096949034) │ │ │ │ -9F565 Compressed Size 000015AD (5549) │ │ │ │ -9F569 Uncompressed Size 00004603 (17923) │ │ │ │ -9F56D Filename Length 0014 (20) │ │ │ │ -9F56F Extra Length 0018 (24) │ │ │ │ -9F571 Comment Length 0000 (0) │ │ │ │ -9F573 Disk Start 0000 (0) │ │ │ │ -9F575 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F577 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F57B Local Header Offset 00000056 (86) │ │ │ │ -9F57F Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F57F: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F593 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F595 Length 0005 (5) │ │ │ │ -9F597 Flags 01 (1) 'Modification' │ │ │ │ -9F598 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F59C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F59E Length 000B (11) │ │ │ │ -9F5A0 Version 01 (1) │ │ │ │ -9F5A1 UID Size 04 (4) │ │ │ │ -9F5A2 UID 00000000 (0) │ │ │ │ -9F5A6 GID Size 04 (4) │ │ │ │ -9F5A7 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F5AB CENTRAL HEADER #3 02014B50 (33639248) │ │ │ │ -9F5AF Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F5B0 Created OS 03 (3) 'Unix' │ │ │ │ -9F5B1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F5B2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F5B3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F5B5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F5B7 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F5BB CRC 741FDE60 (1948245600) │ │ │ │ -9F5BF Compressed Size 000006D6 (1750) │ │ │ │ -9F5C3 Uncompressed Size 00001242 (4674) │ │ │ │ -9F5C7 Filename Length 0013 (19) │ │ │ │ -9F5C9 Extra Length 0018 (24) │ │ │ │ -9F5CB Comment Length 0000 (0) │ │ │ │ -9F5CD Disk Start 0000 (0) │ │ │ │ -9F5CF Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F5D1 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F5D5 Local Header Offset 00001651 (5713) │ │ │ │ -9F5D9 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F5D9: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F5EC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F5EE Length 0005 (5) │ │ │ │ -9F5F0 Flags 01 (1) 'Modification' │ │ │ │ -9F5F1 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F5F5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F5F7 Length 000B (11) │ │ │ │ -9F5F9 Version 01 (1) │ │ │ │ -9F5FA UID Size 04 (4) │ │ │ │ -9F5FB UID 00000000 (0) │ │ │ │ -9F5FF GID Size 04 (4) │ │ │ │ -9F600 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F604 CENTRAL HEADER #4 02014B50 (33639248) │ │ │ │ -9F608 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F609 Created OS 03 (3) 'Unix' │ │ │ │ -9F60A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F60B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F60C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F60E Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F610 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F614 CRC FBBFDC35 (4223654965) │ │ │ │ -9F618 Compressed Size 00002DA6 (11686) │ │ │ │ -9F61C Uncompressed Size 0000D0C0 (53440) │ │ │ │ -9F620 Filename Length 0014 (20) │ │ │ │ -9F622 Extra Length 0018 (24) │ │ │ │ -9F624 Comment Length 0000 (0) │ │ │ │ -9F626 Disk Start 0000 (0) │ │ │ │ -9F628 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F62A Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F62E Local Header Offset 00001D74 (7540) │ │ │ │ -9F632 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F632: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F646 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F648 Length 0005 (5) │ │ │ │ -9F64A Flags 01 (1) 'Modification' │ │ │ │ -9F64B Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F64F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F651 Length 000B (11) │ │ │ │ -9F653 Version 01 (1) │ │ │ │ -9F654 UID Size 04 (4) │ │ │ │ -9F655 UID 00000000 (0) │ │ │ │ -9F659 GID Size 04 (4) │ │ │ │ -9F65A GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F65E CENTRAL HEADER #5 02014B50 (33639248) │ │ │ │ -9F662 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F663 Created OS 03 (3) 'Unix' │ │ │ │ -9F664 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F665 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F666 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F668 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F66A Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F66E CRC AFEB34EA (2951427306) │ │ │ │ -9F672 Compressed Size 000003F0 (1008) │ │ │ │ -9F676 Uncompressed Size 00000877 (2167) │ │ │ │ -9F67A Filename Length 0014 (20) │ │ │ │ -9F67C Extra Length 0018 (24) │ │ │ │ -9F67E Comment Length 0000 (0) │ │ │ │ -9F680 Disk Start 0000 (0) │ │ │ │ -9F682 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F684 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F688 Local Header Offset 00004B68 (19304) │ │ │ │ -9F68C Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F68C: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F6A0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F6A2 Length 0005 (5) │ │ │ │ -9F6A4 Flags 01 (1) 'Modification' │ │ │ │ -9F6A5 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F6A9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F6AB Length 000B (11) │ │ │ │ -9F6AD Version 01 (1) │ │ │ │ -9F6AE UID Size 04 (4) │ │ │ │ -9F6AF UID 00000000 (0) │ │ │ │ -9F6B3 GID Size 04 (4) │ │ │ │ -9F6B4 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F6B8 CENTRAL HEADER #6 02014B50 (33639248) │ │ │ │ -9F6BC Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F6BD Created OS 03 (3) 'Unix' │ │ │ │ -9F6BE Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F6BF Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F6C0 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F6C2 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F6C4 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F6C8 CRC 21AF85A7 (565151143) │ │ │ │ -9F6CC Compressed Size 000001AE (430) │ │ │ │ -9F6D0 Uncompressed Size 000002FE (766) │ │ │ │ -9F6D4 Filename Length 0011 (17) │ │ │ │ -9F6D6 Extra Length 0018 (24) │ │ │ │ -9F6D8 Comment Length 0000 (0) │ │ │ │ -9F6DA Disk Start 0000 (0) │ │ │ │ -9F6DC Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F6DE Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F6E2 Local Header Offset 00004FA6 (20390) │ │ │ │ -9F6E6 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F6E6: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F6F7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F6F9 Length 0005 (5) │ │ │ │ -9F6FB Flags 01 (1) 'Modification' │ │ │ │ -9F6FC Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F700 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F702 Length 000B (11) │ │ │ │ -9F704 Version 01 (1) │ │ │ │ -9F705 UID Size 04 (4) │ │ │ │ -9F706 UID 00000000 (0) │ │ │ │ -9F70A GID Size 04 (4) │ │ │ │ -9F70B GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F70F CENTRAL HEADER #7 02014B50 (33639248) │ │ │ │ -9F713 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F714 Created OS 03 (3) 'Unix' │ │ │ │ -9F715 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F716 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F717 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F719 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F71B Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F71F CRC A0E52D3F (2699373887) │ │ │ │ -9F723 Compressed Size 000020CA (8394) │ │ │ │ -9F727 Uncompressed Size 0000B4B1 (46257) │ │ │ │ -9F72B Filename Length 001B (27) │ │ │ │ -9F72D Extra Length 0018 (24) │ │ │ │ -9F72F Comment Length 0000 (0) │ │ │ │ -9F731 Disk Start 0000 (0) │ │ │ │ -9F733 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F735 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F739 Local Header Offset 0000519F (20895) │ │ │ │ -9F73D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F73D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F758 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F75A Length 0005 (5) │ │ │ │ -9F75C Flags 01 (1) 'Modification' │ │ │ │ -9F75D Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F761 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F763 Length 000B (11) │ │ │ │ -9F765 Version 01 (1) │ │ │ │ -9F766 UID Size 04 (4) │ │ │ │ -9F767 UID 00000000 (0) │ │ │ │ -9F76B GID Size 04 (4) │ │ │ │ -9F76C GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F770 CENTRAL HEADER #8 02014B50 (33639248) │ │ │ │ -9F774 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F775 Created OS 03 (3) 'Unix' │ │ │ │ -9F776 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F777 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F778 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F77A Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F77C Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F780 CRC 2D371F1E (758587166) │ │ │ │ -9F784 Compressed Size 00000E70 (3696) │ │ │ │ -9F788 Uncompressed Size 000030B3 (12467) │ │ │ │ -9F78C Filename Length 001D (29) │ │ │ │ -9F78E Extra Length 0018 (24) │ │ │ │ -9F790 Comment Length 0000 (0) │ │ │ │ -9F792 Disk Start 0000 (0) │ │ │ │ -9F794 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F796 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F79A Local Header Offset 000072BE (29374) │ │ │ │ -9F79E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F79E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F7BB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F7BD Length 0005 (5) │ │ │ │ -9F7BF Flags 01 (1) 'Modification' │ │ │ │ -9F7C0 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F7C4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F7C6 Length 000B (11) │ │ │ │ -9F7C8 Version 01 (1) │ │ │ │ -9F7C9 UID Size 04 (4) │ │ │ │ -9F7CA UID 00000000 (0) │ │ │ │ -9F7CE GID Size 04 (4) │ │ │ │ -9F7CF GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F7D3 CENTRAL HEADER #9 02014B50 (33639248) │ │ │ │ -9F7D7 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F7D8 Created OS 03 (3) 'Unix' │ │ │ │ -9F7D9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F7DA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F7DB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F7DD Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F7DF Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F7E3 CRC 7903D0CB (2030293195) │ │ │ │ -9F7E7 Compressed Size 00000973 (2419) │ │ │ │ -9F7EB Uncompressed Size 00001CB3 (7347) │ │ │ │ -9F7EF Filename Length 0019 (25) │ │ │ │ -9F7F1 Extra Length 0018 (24) │ │ │ │ -9F7F3 Comment Length 0000 (0) │ │ │ │ -9F7F5 Disk Start 0000 (0) │ │ │ │ -9F7F7 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F7F9 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F7FD Local Header Offset 00008185 (33157) │ │ │ │ -9F801 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F801: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F81A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F81C Length 0005 (5) │ │ │ │ -9F81E Flags 01 (1) 'Modification' │ │ │ │ -9F81F Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F823 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F825 Length 000B (11) │ │ │ │ -9F827 Version 01 (1) │ │ │ │ -9F828 UID Size 04 (4) │ │ │ │ -9F829 UID 00000000 (0) │ │ │ │ -9F82D GID Size 04 (4) │ │ │ │ -9F82E GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F832 CENTRAL HEADER #10 02014B50 (33639248) │ │ │ │ -9F836 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F837 Created OS 03 (3) 'Unix' │ │ │ │ -9F838 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F839 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F83A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F83C Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F83E Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F842 CRC 9B28EE90 (2603150992) │ │ │ │ -9F846 Compressed Size 00003880 (14464) │ │ │ │ -9F84A Uncompressed Size 0000F7F5 (63477) │ │ │ │ -9F84E Filename Length 0015 (21) │ │ │ │ -9F850 Extra Length 0018 (24) │ │ │ │ -9F852 Comment Length 0000 (0) │ │ │ │ -9F854 Disk Start 0000 (0) │ │ │ │ -9F856 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F858 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F85C Local Header Offset 00008B4B (35659) │ │ │ │ -9F860 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F860: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F875 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F877 Length 0005 (5) │ │ │ │ -9F879 Flags 01 (1) 'Modification' │ │ │ │ -9F87A Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F87E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F880 Length 000B (11) │ │ │ │ -9F882 Version 01 (1) │ │ │ │ -9F883 UID Size 04 (4) │ │ │ │ -9F884 UID 00000000 (0) │ │ │ │ -9F888 GID Size 04 (4) │ │ │ │ -9F889 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F88D CENTRAL HEADER #11 02014B50 (33639248) │ │ │ │ -9F891 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F892 Created OS 03 (3) 'Unix' │ │ │ │ -9F893 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F894 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F895 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F897 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F899 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F89D CRC A03CCBDB (2688338907) │ │ │ │ -9F8A1 Compressed Size 0000AB09 (43785) │ │ │ │ -9F8A5 Uncompressed Size 0003E052 (254034) │ │ │ │ -9F8A9 Filename Length 0012 (18) │ │ │ │ -9F8AB Extra Length 0018 (24) │ │ │ │ -9F8AD Comment Length 0000 (0) │ │ │ │ -9F8AF Disk Start 0000 (0) │ │ │ │ -9F8B1 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F8B3 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F8B7 Local Header Offset 0000C41A (50202) │ │ │ │ -9F8BB Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F8BB: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F8CD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F8CF Length 0005 (5) │ │ │ │ -9F8D1 Flags 01 (1) 'Modification' │ │ │ │ -9F8D2 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F8D6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F8D8 Length 000B (11) │ │ │ │ -9F8DA Version 01 (1) │ │ │ │ -9F8DB UID Size 04 (4) │ │ │ │ -9F8DC UID 00000000 (0) │ │ │ │ -9F8E0 GID Size 04 (4) │ │ │ │ -9F8E1 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F8E5 CENTRAL HEADER #12 02014B50 (33639248) │ │ │ │ -9F8E9 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F8EA Created OS 03 (3) 'Unix' │ │ │ │ -9F8EB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F8EC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F8ED General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F8EF Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F8F1 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F8F5 CRC 4650ADC7 (1179692487) │ │ │ │ -9F8F9 Compressed Size 00003B0B (15115) │ │ │ │ -9F8FD Uncompressed Size 0001B46D (111725) │ │ │ │ -9F901 Filename Length 0015 (21) │ │ │ │ -9F903 Extra Length 0018 (24) │ │ │ │ -9F905 Comment Length 0000 (0) │ │ │ │ -9F907 Disk Start 0000 (0) │ │ │ │ -9F909 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F90B Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F90F Local Header Offset 00016F6F (94063) │ │ │ │ -9F913 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F913: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F928 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F92A Length 0005 (5) │ │ │ │ -9F92C Flags 01 (1) 'Modification' │ │ │ │ -9F92D Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F931 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F933 Length 000B (11) │ │ │ │ -9F935 Version 01 (1) │ │ │ │ -9F936 UID Size 04 (4) │ │ │ │ -9F937 UID 00000000 (0) │ │ │ │ -9F93B GID Size 04 (4) │ │ │ │ -9F93C GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F940 CENTRAL HEADER #13 02014B50 (33639248) │ │ │ │ -9F944 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F945 Created OS 03 (3) 'Unix' │ │ │ │ -9F946 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F947 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F948 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F94A Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F94C Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F950 CRC A2B5FF2C (2729836332) │ │ │ │ -9F954 Compressed Size 000091C4 (37316) │ │ │ │ -9F958 Uncompressed Size 0003D9DE (252382) │ │ │ │ -9F95C Filename Length 0014 (20) │ │ │ │ -9F95E Extra Length 0018 (24) │ │ │ │ -9F960 Comment Length 0000 (0) │ │ │ │ -9F962 Disk Start 0000 (0) │ │ │ │ -9F964 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F966 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F96A Local Header Offset 0001AAC9 (109257) │ │ │ │ -9F96E Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F96E: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F982 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F984 Length 0005 (5) │ │ │ │ -9F986 Flags 01 (1) 'Modification' │ │ │ │ -9F987 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F98B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F98D Length 000B (11) │ │ │ │ -9F98F Version 01 (1) │ │ │ │ -9F990 UID Size 04 (4) │ │ │ │ -9F991 UID 00000000 (0) │ │ │ │ -9F995 GID Size 04 (4) │ │ │ │ -9F996 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F99A CENTRAL HEADER #14 02014B50 (33639248) │ │ │ │ -9F99E Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F99F Created OS 03 (3) 'Unix' │ │ │ │ -9F9A0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F9A1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F9A2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F9A4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F9A6 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F9AA CRC 49680290 (1231553168) │ │ │ │ -9F9AE Compressed Size 00002A65 (10853) │ │ │ │ -9F9B2 Uncompressed Size 00011520 (70944) │ │ │ │ -9F9B6 Filename Length 0016 (22) │ │ │ │ -9F9B8 Extra Length 0018 (24) │ │ │ │ -9F9BA Comment Length 0000 (0) │ │ │ │ -9F9BC Disk Start 0000 (0) │ │ │ │ -9F9BE Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F9C0 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F9C4 Local Header Offset 00023CDB (146651) │ │ │ │ -9F9C8 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F9C8: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F9DE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F9E0 Length 0005 (5) │ │ │ │ -9F9E2 Flags 01 (1) 'Modification' │ │ │ │ -9F9E3 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9F9E7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F9E9 Length 000B (11) │ │ │ │ -9F9EB Version 01 (1) │ │ │ │ -9F9EC UID Size 04 (4) │ │ │ │ -9F9ED UID 00000000 (0) │ │ │ │ -9F9F1 GID Size 04 (4) │ │ │ │ -9F9F2 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F9F6 CENTRAL HEADER #15 02014B50 (33639248) │ │ │ │ -9F9FA Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F9FB Created OS 03 (3) 'Unix' │ │ │ │ -9F9FC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F9FD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F9FE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FA00 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FA02 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FA06 CRC 29BEF9C0 (700381632) │ │ │ │ -9FA0A Compressed Size 000014DB (5339) │ │ │ │ -9FA0E Uncompressed Size 0000518E (20878) │ │ │ │ -9FA12 Filename Length 001D (29) │ │ │ │ -9FA14 Extra Length 0018 (24) │ │ │ │ -9FA16 Comment Length 0000 (0) │ │ │ │ -9FA18 Disk Start 0000 (0) │ │ │ │ -9FA1A Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FA1C Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FA20 Local Header Offset 00026790 (157584) │ │ │ │ -9FA24 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FA24: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FA41 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FA43 Length 0005 (5) │ │ │ │ -9FA45 Flags 01 (1) 'Modification' │ │ │ │ -9FA46 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FA4A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FA4C Length 000B (11) │ │ │ │ -9FA4E Version 01 (1) │ │ │ │ -9FA4F UID Size 04 (4) │ │ │ │ -9FA50 UID 00000000 (0) │ │ │ │ -9FA54 GID Size 04 (4) │ │ │ │ -9FA55 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FA59 CENTRAL HEADER #16 02014B50 (33639248) │ │ │ │ -9FA5D Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FA5E Created OS 03 (3) 'Unix' │ │ │ │ -9FA5F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FA60 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FA61 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FA63 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FA65 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FA69 CRC B011A27F (2953945727) │ │ │ │ -9FA6D Compressed Size 000039AF (14767) │ │ │ │ -9FA71 Uncompressed Size 0000F080 (61568) │ │ │ │ -9FA75 Filename Length 001C (28) │ │ │ │ -9FA77 Extra Length 0018 (24) │ │ │ │ -9FA79 Comment Length 0000 (0) │ │ │ │ -9FA7B Disk Start 0000 (0) │ │ │ │ -9FA7D Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FA7F Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FA83 Local Header Offset 00027CC2 (163010) │ │ │ │ -9FA87 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FA87: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FAA3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FAA5 Length 0005 (5) │ │ │ │ -9FAA7 Flags 01 (1) 'Modification' │ │ │ │ -9FAA8 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FAAC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FAAE Length 000B (11) │ │ │ │ -9FAB0 Version 01 (1) │ │ │ │ -9FAB1 UID Size 04 (4) │ │ │ │ -9FAB2 UID 00000000 (0) │ │ │ │ -9FAB6 GID Size 04 (4) │ │ │ │ -9FAB7 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FABB CENTRAL HEADER #17 02014B50 (33639248) │ │ │ │ -9FABF Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FAC0 Created OS 03 (3) 'Unix' │ │ │ │ -9FAC1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FAC2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FAC3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FAC5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FAC7 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FACB CRC 7A88ED58 (2055794008) │ │ │ │ -9FACF Compressed Size 000006A3 (1699) │ │ │ │ -9FAD3 Uncompressed Size 000011F5 (4597) │ │ │ │ -9FAD7 Filename Length 001C (28) │ │ │ │ -9FAD9 Extra Length 0018 (24) │ │ │ │ -9FADB Comment Length 0000 (0) │ │ │ │ -9FADD Disk Start 0000 (0) │ │ │ │ -9FADF Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FAE1 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FAE5 Local Header Offset 0002B6C7 (177863) │ │ │ │ -9FAE9 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FAE9: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FB05 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FB07 Length 0005 (5) │ │ │ │ -9FB09 Flags 01 (1) 'Modification' │ │ │ │ -9FB0A Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FB0E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FB10 Length 000B (11) │ │ │ │ -9FB12 Version 01 (1) │ │ │ │ -9FB13 UID Size 04 (4) │ │ │ │ -9FB14 UID 00000000 (0) │ │ │ │ -9FB18 GID Size 04 (4) │ │ │ │ -9FB19 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FB1D CENTRAL HEADER #18 02014B50 (33639248) │ │ │ │ -9FB21 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FB22 Created OS 03 (3) 'Unix' │ │ │ │ -9FB23 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FB24 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FB25 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FB27 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FB29 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FB2D CRC 0FC65721 (264656673) │ │ │ │ -9FB31 Compressed Size 00001081 (4225) │ │ │ │ -9FB35 Uncompressed Size 00004C00 (19456) │ │ │ │ -9FB39 Filename Length 001B (27) │ │ │ │ -9FB3B Extra Length 0018 (24) │ │ │ │ -9FB3D Comment Length 0000 (0) │ │ │ │ -9FB3F Disk Start 0000 (0) │ │ │ │ -9FB41 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FB43 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FB47 Local Header Offset 0002BDC0 (179648) │ │ │ │ -9FB4B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FB4B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FB66 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FB68 Length 0005 (5) │ │ │ │ -9FB6A Flags 01 (1) 'Modification' │ │ │ │ -9FB6B Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FB6F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FB71 Length 000B (11) │ │ │ │ -9FB73 Version 01 (1) │ │ │ │ -9FB74 UID Size 04 (4) │ │ │ │ -9FB75 UID 00000000 (0) │ │ │ │ -9FB79 GID Size 04 (4) │ │ │ │ -9FB7A GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FB7E CENTRAL HEADER #19 02014B50 (33639248) │ │ │ │ -9FB82 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FB83 Created OS 03 (3) 'Unix' │ │ │ │ -9FB84 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FB85 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FB86 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FB88 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FB8A Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FB8E CRC 75FAACB3 (1979362483) │ │ │ │ -9FB92 Compressed Size 000033AB (13227) │ │ │ │ -9FB96 Uncompressed Size 0000BC95 (48277) │ │ │ │ -9FB9A Filename Length 001D (29) │ │ │ │ -9FB9C Extra Length 0018 (24) │ │ │ │ -9FB9E Comment Length 0000 (0) │ │ │ │ -9FBA0 Disk Start 0000 (0) │ │ │ │ -9FBA2 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FBA4 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FBA8 Local Header Offset 0002CE96 (183958) │ │ │ │ -9FBAC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FBAC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FBC9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FBCB Length 0005 (5) │ │ │ │ -9FBCD Flags 01 (1) 'Modification' │ │ │ │ -9FBCE Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FBD2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FBD4 Length 000B (11) │ │ │ │ -9FBD6 Version 01 (1) │ │ │ │ -9FBD7 UID Size 04 (4) │ │ │ │ -9FBD8 UID 00000000 (0) │ │ │ │ -9FBDC GID Size 04 (4) │ │ │ │ -9FBDD GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FBE1 CENTRAL HEADER #20 02014B50 (33639248) │ │ │ │ -9FBE5 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FBE6 Created OS 03 (3) 'Unix' │ │ │ │ -9FBE7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FBE8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FBE9 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FBEB Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FBED Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FBF1 CRC D83D1D51 (3627883857) │ │ │ │ -9FBF5 Compressed Size 00000D6B (3435) │ │ │ │ -9FBF9 Uncompressed Size 0000388E (14478) │ │ │ │ -9FBFD Filename Length 001D (29) │ │ │ │ -9FBFF Extra Length 0018 (24) │ │ │ │ -9FC01 Comment Length 0000 (0) │ │ │ │ -9FC03 Disk Start 0000 (0) │ │ │ │ -9FC05 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FC07 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FC0B Local Header Offset 00030298 (197272) │ │ │ │ -9FC0F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FC0F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FC2C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FC2E Length 0005 (5) │ │ │ │ -9FC30 Flags 01 (1) 'Modification' │ │ │ │ -9FC31 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FC35 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FC37 Length 000B (11) │ │ │ │ -9FC39 Version 01 (1) │ │ │ │ -9FC3A UID Size 04 (4) │ │ │ │ -9FC3B UID 00000000 (0) │ │ │ │ -9FC3F GID Size 04 (4) │ │ │ │ -9FC40 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FC44 CENTRAL HEADER #21 02014B50 (33639248) │ │ │ │ -9FC48 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FC49 Created OS 03 (3) 'Unix' │ │ │ │ -9FC4A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FC4B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FC4C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FC4E Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FC50 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FC54 CRC C751E37B (3344032635) │ │ │ │ -9FC58 Compressed Size 00001C69 (7273) │ │ │ │ -9FC5C Uncompressed Size 0000C187 (49543) │ │ │ │ -9FC60 Filename Length 001A (26) │ │ │ │ -9FC62 Extra Length 0018 (24) │ │ │ │ -9FC64 Comment Length 0000 (0) │ │ │ │ -9FC66 Disk Start 0000 (0) │ │ │ │ -9FC68 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FC6A Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FC6E Local Header Offset 0003105A (200794) │ │ │ │ -9FC72 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FC72: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FC8C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FC8E Length 0005 (5) │ │ │ │ -9FC90 Flags 01 (1) 'Modification' │ │ │ │ -9FC91 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FC95 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FC97 Length 000B (11) │ │ │ │ -9FC99 Version 01 (1) │ │ │ │ -9FC9A UID Size 04 (4) │ │ │ │ -9FC9B UID 00000000 (0) │ │ │ │ -9FC9F GID Size 04 (4) │ │ │ │ -9FCA0 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FCA4 CENTRAL HEADER #22 02014B50 (33639248) │ │ │ │ -9FCA8 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FCA9 Created OS 03 (3) 'Unix' │ │ │ │ -9FCAA Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FCAB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FCAC General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FCAE Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FCB0 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FCB4 CRC C41B95CB (3290142155) │ │ │ │ -9FCB8 Compressed Size 000003A4 (932) │ │ │ │ -9FCBC Uncompressed Size 0000088F (2191) │ │ │ │ -9FCC0 Filename Length 0012 (18) │ │ │ │ -9FCC2 Extra Length 0018 (24) │ │ │ │ -9FCC4 Comment Length 0000 (0) │ │ │ │ -9FCC6 Disk Start 0000 (0) │ │ │ │ -9FCC8 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FCCA Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FCCE Local Header Offset 00032D17 (208151) │ │ │ │ -9FCD2 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FCD2: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FCE4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FCE6 Length 0005 (5) │ │ │ │ -9FCE8 Flags 01 (1) 'Modification' │ │ │ │ -9FCE9 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FCED Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FCEF Length 000B (11) │ │ │ │ -9FCF1 Version 01 (1) │ │ │ │ -9FCF2 UID Size 04 (4) │ │ │ │ -9FCF3 UID 00000000 (0) │ │ │ │ -9FCF7 GID Size 04 (4) │ │ │ │ -9FCF8 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FCFC CENTRAL HEADER #23 02014B50 (33639248) │ │ │ │ -9FD00 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FD01 Created OS 03 (3) 'Unix' │ │ │ │ -9FD02 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FD03 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FD04 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FD06 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FD08 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FD0C CRC 40B68220 (1085702688) │ │ │ │ -9FD10 Compressed Size 000001D4 (468) │ │ │ │ -9FD14 Uncompressed Size 00000312 (786) │ │ │ │ -9FD18 Filename Length 0020 (32) │ │ │ │ -9FD1A Extra Length 0018 (24) │ │ │ │ -9FD1C Comment Length 0000 (0) │ │ │ │ -9FD1E Disk Start 0000 (0) │ │ │ │ -9FD20 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FD22 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FD26 Local Header Offset 00033107 (209159) │ │ │ │ -9FD2A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FD2A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FD4A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FD4C Length 0005 (5) │ │ │ │ -9FD4E Flags 01 (1) 'Modification' │ │ │ │ -9FD4F Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FD53 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FD55 Length 000B (11) │ │ │ │ -9FD57 Version 01 (1) │ │ │ │ -9FD58 UID Size 04 (4) │ │ │ │ -9FD59 UID 00000000 (0) │ │ │ │ -9FD5D GID Size 04 (4) │ │ │ │ -9FD5E GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FD62 CENTRAL HEADER #24 02014B50 (33639248) │ │ │ │ -9FD66 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FD67 Created OS 03 (3) 'Unix' │ │ │ │ -9FD68 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FD69 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FD6A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FD6C Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FD6E Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FD72 CRC AEDAC289 (2933572233) │ │ │ │ -9FD76 Compressed Size 000017AA (6058) │ │ │ │ -9FD7A Uncompressed Size 00009D19 (40217) │ │ │ │ -9FD7E Filename Length 001B (27) │ │ │ │ -9FD80 Extra Length 0018 (24) │ │ │ │ -9FD82 Comment Length 0000 (0) │ │ │ │ -9FD84 Disk Start 0000 (0) │ │ │ │ -9FD86 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FD88 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FD8C Local Header Offset 00033335 (209717) │ │ │ │ -9FD90 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FD90: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FDAB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FDAD Length 0005 (5) │ │ │ │ -9FDAF Flags 01 (1) 'Modification' │ │ │ │ -9FDB0 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FDB4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FDB6 Length 000B (11) │ │ │ │ -9FDB8 Version 01 (1) │ │ │ │ -9FDB9 UID Size 04 (4) │ │ │ │ -9FDBA UID 00000000 (0) │ │ │ │ -9FDBE GID Size 04 (4) │ │ │ │ -9FDBF GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FDC3 CENTRAL HEADER #25 02014B50 (33639248) │ │ │ │ -9FDC7 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FDC8 Created OS 03 (3) 'Unix' │ │ │ │ -9FDC9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FDCA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FDCB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FDCD Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FDCF Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FDD3 CRC 8B976C32 (2341956658) │ │ │ │ -9FDD7 Compressed Size 00001374 (4980) │ │ │ │ -9FDDB Uncompressed Size 00003B67 (15207) │ │ │ │ -9FDDF Filename Length 0015 (21) │ │ │ │ -9FDE1 Extra Length 0018 (24) │ │ │ │ -9FDE3 Comment Length 0000 (0) │ │ │ │ -9FDE5 Disk Start 0000 (0) │ │ │ │ -9FDE7 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FDE9 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FDED Local Header Offset 00034B34 (215860) │ │ │ │ -9FDF1 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FDF1: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FE06 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FE08 Length 0005 (5) │ │ │ │ -9FE0A Flags 01 (1) 'Modification' │ │ │ │ -9FE0B Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FE0F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FE11 Length 000B (11) │ │ │ │ -9FE13 Version 01 (1) │ │ │ │ -9FE14 UID Size 04 (4) │ │ │ │ -9FE15 UID 00000000 (0) │ │ │ │ -9FE19 GID Size 04 (4) │ │ │ │ -9FE1A GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FE1E CENTRAL HEADER #26 02014B50 (33639248) │ │ │ │ -9FE22 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FE23 Created OS 03 (3) 'Unix' │ │ │ │ -9FE24 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FE25 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FE26 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FE28 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FE2A Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FE2E CRC 2ACCA91C (718055708) │ │ │ │ -9FE32 Compressed Size 00000AD3 (2771) │ │ │ │ -9FE36 Uncompressed Size 00002136 (8502) │ │ │ │ -9FE3A Filename Length 0011 (17) │ │ │ │ -9FE3C Extra Length 0018 (24) │ │ │ │ -9FE3E Comment Length 0000 (0) │ │ │ │ -9FE40 Disk Start 0000 (0) │ │ │ │ -9FE42 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FE44 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FE48 Local Header Offset 00035EF7 (220919) │ │ │ │ -9FE4C Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FE4C: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FE5D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FE5F Length 0005 (5) │ │ │ │ -9FE61 Flags 01 (1) 'Modification' │ │ │ │ -9FE62 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FE66 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FE68 Length 000B (11) │ │ │ │ -9FE6A Version 01 (1) │ │ │ │ -9FE6B UID Size 04 (4) │ │ │ │ -9FE6C UID 00000000 (0) │ │ │ │ -9FE70 GID Size 04 (4) │ │ │ │ -9FE71 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FE75 CENTRAL HEADER #27 02014B50 (33639248) │ │ │ │ -9FE79 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FE7A Created OS 03 (3) 'Unix' │ │ │ │ -9FE7B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FE7C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FE7D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FE7F Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FE81 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FE85 CRC 984552B7 (2554679991) │ │ │ │ -9FE89 Compressed Size 000003FE (1022) │ │ │ │ -9FE8D Uncompressed Size 00000F0D (3853) │ │ │ │ -9FE91 Filename Length 0014 (20) │ │ │ │ -9FE93 Extra Length 0018 (24) │ │ │ │ -9FE95 Comment Length 0000 (0) │ │ │ │ -9FE97 Disk Start 0000 (0) │ │ │ │ -9FE99 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FE9B Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FE9F Local Header Offset 00036A15 (223765) │ │ │ │ -9FEA3 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FEA3: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FEB7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FEB9 Length 0005 (5) │ │ │ │ -9FEBB Flags 01 (1) 'Modification' │ │ │ │ -9FEBC Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FEC0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FEC2 Length 000B (11) │ │ │ │ -9FEC4 Version 01 (1) │ │ │ │ -9FEC5 UID Size 04 (4) │ │ │ │ -9FEC6 UID 00000000 (0) │ │ │ │ -9FECA GID Size 04 (4) │ │ │ │ -9FECB GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FECF CENTRAL HEADER #28 02014B50 (33639248) │ │ │ │ -9FED3 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FED4 Created OS 03 (3) 'Unix' │ │ │ │ -9FED5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FED6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FED7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FED9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FEDB Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FEDF CRC F2ED2F43 (4075630403) │ │ │ │ -9FEE3 Compressed Size 00001263 (4707) │ │ │ │ -9FEE7 Uncompressed Size 0000346A (13418) │ │ │ │ -9FEEB Filename Length 0014 (20) │ │ │ │ -9FEED Extra Length 0018 (24) │ │ │ │ -9FEEF Comment Length 0000 (0) │ │ │ │ -9FEF1 Disk Start 0000 (0) │ │ │ │ -9FEF3 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FEF5 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FEF9 Local Header Offset 00036E61 (224865) │ │ │ │ -9FEFD Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FEFD: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FF11 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FF13 Length 0005 (5) │ │ │ │ -9FF15 Flags 01 (1) 'Modification' │ │ │ │ -9FF16 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FF1A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FF1C Length 000B (11) │ │ │ │ -9FF1E Version 01 (1) │ │ │ │ -9FF1F UID Size 04 (4) │ │ │ │ -9FF20 UID 00000000 (0) │ │ │ │ -9FF24 GID Size 04 (4) │ │ │ │ -9FF25 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FF29 CENTRAL HEADER #29 02014B50 (33639248) │ │ │ │ -9FF2D Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FF2E Created OS 03 (3) 'Unix' │ │ │ │ -9FF2F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FF30 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FF31 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FF33 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FF35 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FF39 CRC 3233182B (842209323) │ │ │ │ -9FF3D Compressed Size 00000AD1 (2769) │ │ │ │ -9FF41 Uncompressed Size 00002300 (8960) │ │ │ │ -9FF45 Filename Length 001B (27) │ │ │ │ -9FF47 Extra Length 0018 (24) │ │ │ │ -9FF49 Comment Length 0000 (0) │ │ │ │ -9FF4B Disk Start 0000 (0) │ │ │ │ -9FF4D Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FF4F Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FF53 Local Header Offset 00038112 (229650) │ │ │ │ -9FF57 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FF57: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FF72 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FF74 Length 0005 (5) │ │ │ │ -9FF76 Flags 01 (1) 'Modification' │ │ │ │ -9FF77 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FF7B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FF7D Length 000B (11) │ │ │ │ -9FF7F Version 01 (1) │ │ │ │ -9FF80 UID Size 04 (4) │ │ │ │ -9FF81 UID 00000000 (0) │ │ │ │ -9FF85 GID Size 04 (4) │ │ │ │ -9FF86 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FF8A CENTRAL HEADER #30 02014B50 (33639248) │ │ │ │ -9FF8E Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FF8F Created OS 03 (3) 'Unix' │ │ │ │ -9FF90 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FF91 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FF92 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FF94 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FF96 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FF9A CRC 0BD59D77 (198548855) │ │ │ │ -9FF9E Compressed Size 00000A8E (2702) │ │ │ │ -9FFA2 Uncompressed Size 0000237B (9083) │ │ │ │ -9FFA6 Filename Length 0013 (19) │ │ │ │ -9FFA8 Extra Length 0018 (24) │ │ │ │ -9FFAA Comment Length 0000 (0) │ │ │ │ -9FFAC Disk Start 0000 (0) │ │ │ │ -9FFAE Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FFB0 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FFB4 Local Header Offset 00038C38 (232504) │ │ │ │ -9FFB8 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FFB8: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FFCB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FFCD Length 0005 (5) │ │ │ │ -9FFCF Flags 01 (1) 'Modification' │ │ │ │ -9FFD0 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FFD4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FFD6 Length 000B (11) │ │ │ │ -9FFD8 Version 01 (1) │ │ │ │ -9FFD9 UID Size 04 (4) │ │ │ │ -9FFDA UID 00000000 (0) │ │ │ │ -9FFDE GID Size 04 (4) │ │ │ │ -9FFDF GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FFE3 CENTRAL HEADER #31 02014B50 (33639248) │ │ │ │ -9FFE7 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FFE8 Created OS 03 (3) 'Unix' │ │ │ │ -9FFE9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FFEA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FFEB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FFED Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FFEF Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -9FFF3 CRC 4DABAB86 (1303096198) │ │ │ │ -9FFF7 Compressed Size 000010E5 (4325) │ │ │ │ -9FFFB Uncompressed Size 00005611 (22033) │ │ │ │ -9FFFF Filename Length 000F (15) │ │ │ │ -A0001 Extra Length 0018 (24) │ │ │ │ -A0003 Comment Length 0000 (0) │ │ │ │ -A0005 Disk Start 0000 (0) │ │ │ │ -A0007 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0009 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A000D Local Header Offset 00039713 (235283) │ │ │ │ -A0011 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0011: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0020 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0022 Length 0005 (5) │ │ │ │ -A0024 Flags 01 (1) 'Modification' │ │ │ │ -A0025 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0029 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A002B Length 000B (11) │ │ │ │ -A002D Version 01 (1) │ │ │ │ -A002E UID Size 04 (4) │ │ │ │ -A002F UID 00000000 (0) │ │ │ │ -A0033 GID Size 04 (4) │ │ │ │ -A0034 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0038 CENTRAL HEADER #32 02014B50 (33639248) │ │ │ │ -A003C Created Zip Spec 3D (61) '6.1' │ │ │ │ -A003D Created OS 03 (3) 'Unix' │ │ │ │ -A003E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A003F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0040 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0042 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0044 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0048 CRC C70CE575 (3339511157) │ │ │ │ -A004C Compressed Size 0000066B (1643) │ │ │ │ -A0050 Uncompressed Size 000018E0 (6368) │ │ │ │ -A0054 Filename Length 000F (15) │ │ │ │ -A0056 Extra Length 0018 (24) │ │ │ │ -A0058 Comment Length 0000 (0) │ │ │ │ -A005A Disk Start 0000 (0) │ │ │ │ -A005C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A005E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0062 Local Header Offset 0003A841 (239681) │ │ │ │ -A0066 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0066: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0075 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0077 Length 0005 (5) │ │ │ │ -A0079 Flags 01 (1) 'Modification' │ │ │ │ -A007A Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A007E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0080 Length 000B (11) │ │ │ │ -A0082 Version 01 (1) │ │ │ │ -A0083 UID Size 04 (4) │ │ │ │ -A0084 UID 00000000 (0) │ │ │ │ -A0088 GID Size 04 (4) │ │ │ │ -A0089 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A008D CENTRAL HEADER #33 02014B50 (33639248) │ │ │ │ -A0091 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0092 Created OS 03 (3) 'Unix' │ │ │ │ -A0093 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0094 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0095 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0097 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0099 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A009D CRC B33717CF (3006732239) │ │ │ │ -A00A1 Compressed Size 00001A4B (6731) │ │ │ │ -A00A5 Uncompressed Size 000064F3 (25843) │ │ │ │ -A00A9 Filename Length 0013 (19) │ │ │ │ -A00AB Extra Length 0018 (24) │ │ │ │ -A00AD Comment Length 0000 (0) │ │ │ │ -A00AF Disk Start 0000 (0) │ │ │ │ -A00B1 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A00B3 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A00B7 Local Header Offset 0003AEF5 (241397) │ │ │ │ -A00BB Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA00BB: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A00CE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A00D0 Length 0005 (5) │ │ │ │ -A00D2 Flags 01 (1) 'Modification' │ │ │ │ -A00D3 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A00D7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A00D9 Length 000B (11) │ │ │ │ -A00DB Version 01 (1) │ │ │ │ -A00DC UID Size 04 (4) │ │ │ │ -A00DD UID 00000000 (0) │ │ │ │ -A00E1 GID Size 04 (4) │ │ │ │ -A00E2 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A00E6 CENTRAL HEADER #34 02014B50 (33639248) │ │ │ │ -A00EA Created Zip Spec 3D (61) '6.1' │ │ │ │ -A00EB Created OS 03 (3) 'Unix' │ │ │ │ -A00EC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A00ED Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A00EE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A00F0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A00F2 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A00F6 CRC BA8C2020 (3129745440) │ │ │ │ -A00FA Compressed Size 000009A7 (2471) │ │ │ │ -A00FE Uncompressed Size 00001B65 (7013) │ │ │ │ -A0102 Filename Length 0010 (16) │ │ │ │ -A0104 Extra Length 0018 (24) │ │ │ │ -A0106 Comment Length 0000 (0) │ │ │ │ -A0108 Disk Start 0000 (0) │ │ │ │ -A010A Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A010C Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0110 Local Header Offset 0003C98D (248205) │ │ │ │ -A0114 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0114: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0124 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0126 Length 0005 (5) │ │ │ │ -A0128 Flags 01 (1) 'Modification' │ │ │ │ -A0129 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A012D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A012F Length 000B (11) │ │ │ │ -A0131 Version 01 (1) │ │ │ │ -A0132 UID Size 04 (4) │ │ │ │ -A0133 UID 00000000 (0) │ │ │ │ -A0137 GID Size 04 (4) │ │ │ │ -A0138 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A013C CENTRAL HEADER #35 02014B50 (33639248) │ │ │ │ -A0140 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0141 Created OS 03 (3) 'Unix' │ │ │ │ -A0142 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0143 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0144 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0146 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0148 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A014C CRC 61869F50 (1636212560) │ │ │ │ -A0150 Compressed Size 000006B9 (1721) │ │ │ │ -A0154 Uncompressed Size 00001566 (5478) │ │ │ │ -A0158 Filename Length 0012 (18) │ │ │ │ -A015A Extra Length 0018 (24) │ │ │ │ -A015C Comment Length 0000 (0) │ │ │ │ -A015E Disk Start 0000 (0) │ │ │ │ -A0160 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0162 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0166 Local Header Offset 0003D37E (250750) │ │ │ │ -A016A Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA016A: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A017C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A017E Length 0005 (5) │ │ │ │ -A0180 Flags 01 (1) 'Modification' │ │ │ │ -A0181 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0185 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0187 Length 000B (11) │ │ │ │ -A0189 Version 01 (1) │ │ │ │ -A018A UID Size 04 (4) │ │ │ │ -A018B UID 00000000 (0) │ │ │ │ -A018F GID Size 04 (4) │ │ │ │ -A0190 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0194 CENTRAL HEADER #36 02014B50 (33639248) │ │ │ │ -A0198 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0199 Created OS 03 (3) 'Unix' │ │ │ │ -A019A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A019B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A019C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A019E Compression Method 0008 (8) 'Deflated' │ │ │ │ -A01A0 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A01A4 CRC 16F2754B (384988491) │ │ │ │ -A01A8 Compressed Size 00002A14 (10772) │ │ │ │ -A01AC Uncompressed Size 0000B1DD (45533) │ │ │ │ -A01B0 Filename Length 0010 (16) │ │ │ │ -A01B2 Extra Length 0018 (24) │ │ │ │ -A01B4 Comment Length 0000 (0) │ │ │ │ -A01B6 Disk Start 0000 (0) │ │ │ │ -A01B8 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A01BA Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A01BE Local Header Offset 0003DA83 (252547) │ │ │ │ -A01C2 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA01C2: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A01D2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A01D4 Length 0005 (5) │ │ │ │ -A01D6 Flags 01 (1) 'Modification' │ │ │ │ -A01D7 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A01DB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A01DD Length 000B (11) │ │ │ │ -A01DF Version 01 (1) │ │ │ │ -A01E0 UID Size 04 (4) │ │ │ │ -A01E1 UID 00000000 (0) │ │ │ │ -A01E5 GID Size 04 (4) │ │ │ │ -A01E6 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A01EA CENTRAL HEADER #37 02014B50 (33639248) │ │ │ │ -A01EE Created Zip Spec 3D (61) '6.1' │ │ │ │ -A01EF Created OS 03 (3) 'Unix' │ │ │ │ -A01F0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A01F1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A01F2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A01F4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A01F6 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A01FA CRC 23389B09 (590912265) │ │ │ │ -A01FE Compressed Size 00001E89 (7817) │ │ │ │ -A0202 Uncompressed Size 00009AAB (39595) │ │ │ │ -A0206 Filename Length 0012 (18) │ │ │ │ -A0208 Extra Length 0018 (24) │ │ │ │ -A020A Comment Length 0000 (0) │ │ │ │ -A020C Disk Start 0000 (0) │ │ │ │ -A020E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0210 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0214 Local Header Offset 000404E1 (263393) │ │ │ │ -A0218 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0218: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A022A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A022C Length 0005 (5) │ │ │ │ -A022E Flags 01 (1) 'Modification' │ │ │ │ -A022F Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0233 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0235 Length 000B (11) │ │ │ │ -A0237 Version 01 (1) │ │ │ │ -A0238 UID Size 04 (4) │ │ │ │ -A0239 UID 00000000 (0) │ │ │ │ -A023D GID Size 04 (4) │ │ │ │ -A023E GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0242 CENTRAL HEADER #38 02014B50 (33639248) │ │ │ │ -A0246 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0247 Created OS 03 (3) 'Unix' │ │ │ │ -A0248 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0249 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A024A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A024C Compression Method 0008 (8) 'Deflated' │ │ │ │ -A024E Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0252 CRC 13847F5A (327450458) │ │ │ │ -A0256 Compressed Size 0000147C (5244) │ │ │ │ -A025A Uncompressed Size 00007AD0 (31440) │ │ │ │ -A025E Filename Length 0018 (24) │ │ │ │ -A0260 Extra Length 0018 (24) │ │ │ │ -A0262 Comment Length 0000 (0) │ │ │ │ -A0264 Disk Start 0000 (0) │ │ │ │ -A0266 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0268 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A026C Local Header Offset 000423B6 (271286) │ │ │ │ -A0270 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0270: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0288 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A028A Length 0005 (5) │ │ │ │ -A028C Flags 01 (1) 'Modification' │ │ │ │ -A028D Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0291 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0293 Length 000B (11) │ │ │ │ -A0295 Version 01 (1) │ │ │ │ -A0296 UID Size 04 (4) │ │ │ │ -A0297 UID 00000000 (0) │ │ │ │ -A029B GID Size 04 (4) │ │ │ │ -A029C GID 00000000 (0) │ │ │ │ - │ │ │ │ -A02A0 CENTRAL HEADER #39 02014B50 (33639248) │ │ │ │ -A02A4 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A02A5 Created OS 03 (3) 'Unix' │ │ │ │ -A02A6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A02A7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A02A8 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A02AA Compression Method 0008 (8) 'Deflated' │ │ │ │ -A02AC Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A02B0 CRC 021B6F33 (35352371) │ │ │ │ -A02B4 Compressed Size 000018DA (6362) │ │ │ │ -A02B8 Uncompressed Size 0000A83A (43066) │ │ │ │ -A02BC Filename Length 001F (31) │ │ │ │ -A02BE Extra Length 0018 (24) │ │ │ │ -A02C0 Comment Length 0000 (0) │ │ │ │ -A02C2 Disk Start 0000 (0) │ │ │ │ -A02C4 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A02C6 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A02CA Local Header Offset 00043884 (276612) │ │ │ │ -A02CE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA02CE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A02ED Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A02EF Length 0005 (5) │ │ │ │ -A02F1 Flags 01 (1) 'Modification' │ │ │ │ -A02F2 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A02F6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A02F8 Length 000B (11) │ │ │ │ -A02FA Version 01 (1) │ │ │ │ -A02FB UID Size 04 (4) │ │ │ │ -A02FC UID 00000000 (0) │ │ │ │ -A0300 GID Size 04 (4) │ │ │ │ -A0301 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0305 CENTRAL HEADER #40 02014B50 (33639248) │ │ │ │ -A0309 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A030A Created OS 03 (3) 'Unix' │ │ │ │ -A030B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A030C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A030D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A030F Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0311 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0315 CRC 8E4EBF9E (2387525534) │ │ │ │ -A0319 Compressed Size 000003F8 (1016) │ │ │ │ -A031D Uncompressed Size 000008A4 (2212) │ │ │ │ -A0321 Filename Length 001E (30) │ │ │ │ -A0323 Extra Length 0018 (24) │ │ │ │ -A0325 Comment Length 0000 (0) │ │ │ │ -A0327 Disk Start 0000 (0) │ │ │ │ -A0329 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A032B Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A032F Local Header Offset 000451B7 (283063) │ │ │ │ -A0333 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0333: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0351 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0353 Length 0005 (5) │ │ │ │ -A0355 Flags 01 (1) 'Modification' │ │ │ │ -A0356 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A035A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A035C Length 000B (11) │ │ │ │ -A035E Version 01 (1) │ │ │ │ -A035F UID Size 04 (4) │ │ │ │ -A0360 UID 00000000 (0) │ │ │ │ -A0364 GID Size 04 (4) │ │ │ │ -A0365 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0369 CENTRAL HEADER #41 02014B50 (33639248) │ │ │ │ -A036D Created Zip Spec 3D (61) '6.1' │ │ │ │ -A036E Created OS 03 (3) 'Unix' │ │ │ │ -A036F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0370 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0371 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0373 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0375 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0379 CRC D9FD2FF3 (3657248755) │ │ │ │ -A037D Compressed Size 00004296 (17046) │ │ │ │ -A0381 Uncompressed Size 0000D8E8 (55528) │ │ │ │ -A0385 Filename Length 0013 (19) │ │ │ │ -A0387 Extra Length 0018 (24) │ │ │ │ -A0389 Comment Length 0000 (0) │ │ │ │ -A038B Disk Start 0000 (0) │ │ │ │ -A038D Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A038F Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0393 Local Header Offset 00045607 (284167) │ │ │ │ -A0397 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0397: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A03AA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A03AC Length 0005 (5) │ │ │ │ -A03AE Flags 01 (1) 'Modification' │ │ │ │ -A03AF Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A03B3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A03B5 Length 000B (11) │ │ │ │ -A03B7 Version 01 (1) │ │ │ │ -A03B8 UID Size 04 (4) │ │ │ │ -A03B9 UID 00000000 (0) │ │ │ │ -A03BD GID Size 04 (4) │ │ │ │ -A03BE GID 00000000 (0) │ │ │ │ - │ │ │ │ -A03C2 CENTRAL HEADER #42 02014B50 (33639248) │ │ │ │ -A03C6 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A03C7 Created OS 03 (3) 'Unix' │ │ │ │ -A03C8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A03C9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A03CA General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A03CC Compression Method 0008 (8) 'Deflated' │ │ │ │ -A03CE Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A03D2 CRC A60BB6F3 (2785785587) │ │ │ │ -A03D6 Compressed Size 000026C4 (9924) │ │ │ │ -A03DA Uncompressed Size 00006E46 (28230) │ │ │ │ -A03DE Filename Length 0019 (25) │ │ │ │ -A03E0 Extra Length 0018 (24) │ │ │ │ -A03E2 Comment Length 0000 (0) │ │ │ │ -A03E4 Disk Start 0000 (0) │ │ │ │ -A03E6 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A03E8 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A03EC Local Header Offset 000498EA (301290) │ │ │ │ -A03F0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA03F0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0409 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A040B Length 0005 (5) │ │ │ │ -A040D Flags 01 (1) 'Modification' │ │ │ │ -A040E Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0412 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0414 Length 000B (11) │ │ │ │ -A0416 Version 01 (1) │ │ │ │ -A0417 UID Size 04 (4) │ │ │ │ -A0418 UID 00000000 (0) │ │ │ │ -A041C GID Size 04 (4) │ │ │ │ -A041D GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0421 CENTRAL HEADER #43 02014B50 (33639248) │ │ │ │ -A0425 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0426 Created OS 03 (3) 'Unix' │ │ │ │ -A0427 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0428 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0429 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A042B Compression Method 0008 (8) 'Deflated' │ │ │ │ -A042D Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0431 CRC A84F7DF4 (2823781876) │ │ │ │ -A0435 Compressed Size 0000273B (10043) │ │ │ │ -A0439 Uncompressed Size 00008B84 (35716) │ │ │ │ -A043D Filename Length 0019 (25) │ │ │ │ -A043F Extra Length 0018 (24) │ │ │ │ -A0441 Comment Length 0000 (0) │ │ │ │ -A0443 Disk Start 0000 (0) │ │ │ │ -A0445 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0447 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A044B Local Header Offset 0004C001 (311297) │ │ │ │ -A044F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA044F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0468 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A046A Length 0005 (5) │ │ │ │ -A046C Flags 01 (1) 'Modification' │ │ │ │ -A046D Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0471 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0473 Length 000B (11) │ │ │ │ -A0475 Version 01 (1) │ │ │ │ -A0476 UID Size 04 (4) │ │ │ │ -A0477 UID 00000000 (0) │ │ │ │ -A047B GID Size 04 (4) │ │ │ │ -A047C GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0480 CENTRAL HEADER #44 02014B50 (33639248) │ │ │ │ -A0484 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0485 Created OS 03 (3) 'Unix' │ │ │ │ -A0486 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0487 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0488 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A048A Compression Method 0008 (8) 'Deflated' │ │ │ │ -A048C Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0490 CRC 20889E86 (545824390) │ │ │ │ -A0494 Compressed Size 00000CF1 (3313) │ │ │ │ -A0498 Uncompressed Size 0000517B (20859) │ │ │ │ -A049C Filename Length 0021 (33) │ │ │ │ -A049E Extra Length 0018 (24) │ │ │ │ -A04A0 Comment Length 0000 (0) │ │ │ │ -A04A2 Disk Start 0000 (0) │ │ │ │ -A04A4 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A04A6 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A04AA Local Header Offset 0004E78F (321423) │ │ │ │ -A04AE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA04AE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A04CF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A04D1 Length 0005 (5) │ │ │ │ -A04D3 Flags 01 (1) 'Modification' │ │ │ │ -A04D4 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A04D8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A04DA Length 000B (11) │ │ │ │ -A04DC Version 01 (1) │ │ │ │ -A04DD UID Size 04 (4) │ │ │ │ -A04DE UID 00000000 (0) │ │ │ │ -A04E2 GID Size 04 (4) │ │ │ │ -A04E3 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A04E7 CENTRAL HEADER #45 02014B50 (33639248) │ │ │ │ -A04EB Created Zip Spec 3D (61) '6.1' │ │ │ │ -A04EC Created OS 03 (3) 'Unix' │ │ │ │ -A04ED Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A04EE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A04EF General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A04F1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A04F3 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A04F7 CRC 4527F9F0 (1160247792) │ │ │ │ -A04FB Compressed Size 00000469 (1129) │ │ │ │ -A04FF Uncompressed Size 00000932 (2354) │ │ │ │ -A0503 Filename Length 001B (27) │ │ │ │ -A0505 Extra Length 0018 (24) │ │ │ │ -A0507 Comment Length 0000 (0) │ │ │ │ -A0509 Disk Start 0000 (0) │ │ │ │ -A050B Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A050D Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0511 Local Header Offset 0004F4DB (324827) │ │ │ │ -A0515 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0515: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0530 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0532 Length 0005 (5) │ │ │ │ -A0534 Flags 01 (1) 'Modification' │ │ │ │ -A0535 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0539 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A053B Length 000B (11) │ │ │ │ -A053D Version 01 (1) │ │ │ │ -A053E UID Size 04 (4) │ │ │ │ -A053F UID 00000000 (0) │ │ │ │ -A0543 GID Size 04 (4) │ │ │ │ -A0544 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0548 CENTRAL HEADER #46 02014B50 (33639248) │ │ │ │ -A054C Created Zip Spec 3D (61) '6.1' │ │ │ │ -A054D Created OS 03 (3) 'Unix' │ │ │ │ -A054E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A054F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0550 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0552 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0554 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0558 CRC 51FBB037 (1375449143) │ │ │ │ -A055C Compressed Size 000016F1 (5873) │ │ │ │ -A0560 Uncompressed Size 00007A6E (31342) │ │ │ │ -A0564 Filename Length 001F (31) │ │ │ │ -A0566 Extra Length 0018 (24) │ │ │ │ -A0568 Comment Length 0000 (0) │ │ │ │ -A056A Disk Start 0000 (0) │ │ │ │ -A056C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A056E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0572 Local Header Offset 0004F999 (326041) │ │ │ │ -A0576 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0576: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0595 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0597 Length 0005 (5) │ │ │ │ -A0599 Flags 01 (1) 'Modification' │ │ │ │ -A059A Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A059E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A05A0 Length 000B (11) │ │ │ │ -A05A2 Version 01 (1) │ │ │ │ -A05A3 UID Size 04 (4) │ │ │ │ -A05A4 UID 00000000 (0) │ │ │ │ -A05A8 GID Size 04 (4) │ │ │ │ -A05A9 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A05AD CENTRAL HEADER #47 02014B50 (33639248) │ │ │ │ -A05B1 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A05B2 Created OS 03 (3) 'Unix' │ │ │ │ -A05B3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A05B4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A05B5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A05B7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A05B9 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A05BD CRC 2414B628 (605337128) │ │ │ │ -A05C1 Compressed Size 00004161 (16737) │ │ │ │ -A05C5 Uncompressed Size 0001D160 (119136) │ │ │ │ -A05C9 Filename Length 0010 (16) │ │ │ │ -A05CB Extra Length 0018 (24) │ │ │ │ -A05CD Comment Length 0000 (0) │ │ │ │ -A05CF Disk Start 0000 (0) │ │ │ │ -A05D1 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A05D3 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A05D7 Local Header Offset 000510E3 (332003) │ │ │ │ -A05DB Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA05DB: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A05EB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A05ED Length 0005 (5) │ │ │ │ -A05EF Flags 01 (1) 'Modification' │ │ │ │ -A05F0 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A05F4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A05F6 Length 000B (11) │ │ │ │ -A05F8 Version 01 (1) │ │ │ │ -A05F9 UID Size 04 (4) │ │ │ │ -A05FA UID 00000000 (0) │ │ │ │ -A05FE GID Size 04 (4) │ │ │ │ -A05FF GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0603 CENTRAL HEADER #48 02014B50 (33639248) │ │ │ │ -A0607 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0608 Created OS 03 (3) 'Unix' │ │ │ │ -A0609 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A060A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A060B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A060D Compression Method 0008 (8) 'Deflated' │ │ │ │ -A060F Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0613 CRC 1C486A07 (474507783) │ │ │ │ -A0617 Compressed Size 00000A98 (2712) │ │ │ │ -A061B Uncompressed Size 00002106 (8454) │ │ │ │ -A061F Filename Length 0014 (20) │ │ │ │ -A0621 Extra Length 0018 (24) │ │ │ │ -A0623 Comment Length 0000 (0) │ │ │ │ -A0625 Disk Start 0000 (0) │ │ │ │ -A0627 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0629 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A062D Local Header Offset 0005528E (348814) │ │ │ │ -A0631 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0631: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0645 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0647 Length 0005 (5) │ │ │ │ -A0649 Flags 01 (1) 'Modification' │ │ │ │ -A064A Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A064E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0650 Length 000B (11) │ │ │ │ -A0652 Version 01 (1) │ │ │ │ -A0653 UID Size 04 (4) │ │ │ │ -A0654 UID 00000000 (0) │ │ │ │ -A0658 GID Size 04 (4) │ │ │ │ -A0659 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A065D CENTRAL HEADER #49 02014B50 (33639248) │ │ │ │ -A0661 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0662 Created OS 03 (3) 'Unix' │ │ │ │ -A0663 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0664 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0665 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0667 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0669 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A066D CRC 7F22DF82 (2132991874) │ │ │ │ -A0671 Compressed Size 0000AD78 (44408) │ │ │ │ -A0675 Uncompressed Size 0003EB1B (256795) │ │ │ │ -A0679 Filename Length 0017 (23) │ │ │ │ -A067B Extra Length 0018 (24) │ │ │ │ -A067D Comment Length 0000 (0) │ │ │ │ -A067F Disk Start 0000 (0) │ │ │ │ -A0681 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0683 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0687 Local Header Offset 00055D74 (351604) │ │ │ │ -A068B Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA068B: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A06A2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A06A4 Length 0005 (5) │ │ │ │ -A06A6 Flags 01 (1) 'Modification' │ │ │ │ -A06A7 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A06AB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A06AD Length 000B (11) │ │ │ │ -A06AF Version 01 (1) │ │ │ │ -A06B0 UID Size 04 (4) │ │ │ │ -A06B1 UID 00000000 (0) │ │ │ │ -A06B5 GID Size 04 (4) │ │ │ │ -A06B6 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A06BA CENTRAL HEADER #50 02014B50 (33639248) │ │ │ │ -A06BE Created Zip Spec 3D (61) '6.1' │ │ │ │ -A06BF Created OS 03 (3) 'Unix' │ │ │ │ -A06C0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A06C1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A06C2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A06C4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A06C6 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A06CA CRC A0A15D62 (2694929762) │ │ │ │ -A06CE Compressed Size 00000464 (1124) │ │ │ │ -A06D2 Uncompressed Size 00000DF4 (3572) │ │ │ │ -A06D6 Filename Length 0013 (19) │ │ │ │ -A06D8 Extra Length 0018 (24) │ │ │ │ -A06DA Comment Length 0000 (0) │ │ │ │ -A06DC Disk Start 0000 (0) │ │ │ │ -A06DE Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A06E0 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A06E4 Local Header Offset 00060B3D (396093) │ │ │ │ -A06E8 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA06E8: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A06FB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A06FD Length 0005 (5) │ │ │ │ -A06FF Flags 01 (1) 'Modification' │ │ │ │ -A0700 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0704 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0706 Length 000B (11) │ │ │ │ -A0708 Version 01 (1) │ │ │ │ -A0709 UID Size 04 (4) │ │ │ │ -A070A UID 00000000 (0) │ │ │ │ -A070E GID Size 04 (4) │ │ │ │ -A070F GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0713 CENTRAL HEADER #51 02014B50 (33639248) │ │ │ │ -A0717 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0718 Created OS 03 (3) 'Unix' │ │ │ │ -A0719 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A071A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A071B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A071D Compression Method 0008 (8) 'Deflated' │ │ │ │ -A071F Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0723 CRC D825044E (3626304590) │ │ │ │ -A0727 Compressed Size 000014D6 (5334) │ │ │ │ -A072B Uncompressed Size 00006893 (26771) │ │ │ │ -A072F Filename Length 0012 (18) │ │ │ │ -A0731 Extra Length 0018 (24) │ │ │ │ -A0733 Comment Length 0000 (0) │ │ │ │ -A0735 Disk Start 0000 (0) │ │ │ │ -A0737 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0739 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A073D Local Header Offset 00060FEE (397294) │ │ │ │ -A0741 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0741: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0753 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0755 Length 0005 (5) │ │ │ │ -A0757 Flags 01 (1) 'Modification' │ │ │ │ -A0758 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A075C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A075E Length 000B (11) │ │ │ │ -A0760 Version 01 (1) │ │ │ │ -A0761 UID Size 04 (4) │ │ │ │ -A0762 UID 00000000 (0) │ │ │ │ -A0766 GID Size 04 (4) │ │ │ │ -A0767 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A076B CENTRAL HEADER #52 02014B50 (33639248) │ │ │ │ -A076F Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0770 Created OS 03 (3) 'Unix' │ │ │ │ -A0771 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0772 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0773 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0775 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0777 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A077B CRC C193E548 (3247695176) │ │ │ │ -A077F Compressed Size 000011F0 (4592) │ │ │ │ -A0783 Uncompressed Size 0000410D (16653) │ │ │ │ -A0787 Filename Length 0012 (18) │ │ │ │ -A0789 Extra Length 0018 (24) │ │ │ │ -A078B Comment Length 0000 (0) │ │ │ │ -A078D Disk Start 0000 (0) │ │ │ │ -A078F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0791 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0795 Local Header Offset 00062510 (402704) │ │ │ │ -A0799 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0799: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A07AB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A07AD Length 0005 (5) │ │ │ │ -A07AF Flags 01 (1) 'Modification' │ │ │ │ -A07B0 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A07B4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A07B6 Length 000B (11) │ │ │ │ -A07B8 Version 01 (1) │ │ │ │ -A07B9 UID Size 04 (4) │ │ │ │ -A07BA UID 00000000 (0) │ │ │ │ -A07BE GID Size 04 (4) │ │ │ │ -A07BF GID 00000000 (0) │ │ │ │ - │ │ │ │ -A07C3 CENTRAL HEADER #53 02014B50 (33639248) │ │ │ │ -A07C7 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A07C8 Created OS 03 (3) 'Unix' │ │ │ │ -A07C9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A07CA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A07CB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A07CD Compression Method 0008 (8) 'Deflated' │ │ │ │ -A07CF Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A07D3 CRC 10C49B8F (281320335) │ │ │ │ -A07D7 Compressed Size 000009D9 (2521) │ │ │ │ -A07DB Uncompressed Size 0000352A (13610) │ │ │ │ -A07DF Filename Length 0019 (25) │ │ │ │ -A07E1 Extra Length 0018 (24) │ │ │ │ -A07E3 Comment Length 0000 (0) │ │ │ │ -A07E5 Disk Start 0000 (0) │ │ │ │ -A07E7 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A07E9 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A07ED Local Header Offset 0006374C (407372) │ │ │ │ -A07F1 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA07F1: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A080A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A080C Length 0005 (5) │ │ │ │ -A080E Flags 01 (1) 'Modification' │ │ │ │ -A080F Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0813 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0815 Length 000B (11) │ │ │ │ -A0817 Version 01 (1) │ │ │ │ -A0818 UID Size 04 (4) │ │ │ │ -A0819 UID 00000000 (0) │ │ │ │ -A081D GID Size 04 (4) │ │ │ │ -A081E GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0822 CENTRAL HEADER #54 02014B50 (33639248) │ │ │ │ -A0826 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0827 Created OS 03 (3) 'Unix' │ │ │ │ -A0828 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0829 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A082A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A082C Compression Method 0008 (8) 'Deflated' │ │ │ │ -A082E Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0832 CRC F2CB28F8 (4073400568) │ │ │ │ -A0836 Compressed Size 00002023 (8227) │ │ │ │ -A083A Uncompressed Size 00010945 (67909) │ │ │ │ -A083E Filename Length 0019 (25) │ │ │ │ -A0840 Extra Length 0018 (24) │ │ │ │ -A0842 Comment Length 0000 (0) │ │ │ │ -A0844 Disk Start 0000 (0) │ │ │ │ -A0846 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0848 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A084C Local Header Offset 00064178 (409976) │ │ │ │ -A0850 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0850: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0869 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A086B Length 0005 (5) │ │ │ │ -A086D Flags 01 (1) 'Modification' │ │ │ │ -A086E Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0872 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0874 Length 000B (11) │ │ │ │ -A0876 Version 01 (1) │ │ │ │ -A0877 UID Size 04 (4) │ │ │ │ -A0878 UID 00000000 (0) │ │ │ │ -A087C GID Size 04 (4) │ │ │ │ -A087D GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0881 CENTRAL HEADER #55 02014B50 (33639248) │ │ │ │ -A0885 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0886 Created OS 03 (3) 'Unix' │ │ │ │ -A0887 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0888 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0889 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A088B Compression Method 0008 (8) 'Deflated' │ │ │ │ -A088D Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0891 CRC D0372DB1 (3493277105) │ │ │ │ -A0895 Compressed Size 0000177F (6015) │ │ │ │ -A0899 Uncompressed Size 0000472D (18221) │ │ │ │ -A089D Filename Length 0014 (20) │ │ │ │ -A089F Extra Length 0018 (24) │ │ │ │ -A08A1 Comment Length 0000 (0) │ │ │ │ -A08A3 Disk Start 0000 (0) │ │ │ │ -A08A5 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A08A7 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A08AB Local Header Offset 000661EE (418286) │ │ │ │ -A08AF Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA08AF: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A08C3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A08C5 Length 0005 (5) │ │ │ │ -A08C7 Flags 01 (1) 'Modification' │ │ │ │ -A08C8 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A08CC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A08CE Length 000B (11) │ │ │ │ -A08D0 Version 01 (1) │ │ │ │ -A08D1 UID Size 04 (4) │ │ │ │ -A08D2 UID 00000000 (0) │ │ │ │ -A08D6 GID Size 04 (4) │ │ │ │ -A08D7 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A08DB CENTRAL HEADER #56 02014B50 (33639248) │ │ │ │ -A08DF Created Zip Spec 3D (61) '6.1' │ │ │ │ -A08E0 Created OS 03 (3) 'Unix' │ │ │ │ -A08E1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A08E2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A08E3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A08E5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A08E7 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A08EB CRC FAE30038 (4209180728) │ │ │ │ -A08EF Compressed Size 0000040B (1035) │ │ │ │ -A08F3 Uncompressed Size 00000826 (2086) │ │ │ │ -A08F7 Filename Length 001C (28) │ │ │ │ -A08F9 Extra Length 0018 (24) │ │ │ │ -A08FB Comment Length 0000 (0) │ │ │ │ -A08FD Disk Start 0000 (0) │ │ │ │ -A08FF Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0901 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0905 Local Header Offset 000679BB (424379) │ │ │ │ -A0909 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0909: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0925 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0927 Length 0005 (5) │ │ │ │ -A0929 Flags 01 (1) 'Modification' │ │ │ │ -A092A Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A092E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0930 Length 000B (11) │ │ │ │ -A0932 Version 01 (1) │ │ │ │ -A0933 UID Size 04 (4) │ │ │ │ -A0934 UID 00000000 (0) │ │ │ │ -A0938 GID Size 04 (4) │ │ │ │ -A0939 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A093D CENTRAL HEADER #57 02014B50 (33639248) │ │ │ │ -A0941 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0942 Created OS 03 (3) 'Unix' │ │ │ │ -A0943 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0944 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0945 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0947 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0949 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A094D CRC 60701273 (1617957491) │ │ │ │ -A0951 Compressed Size 00002499 (9369) │ │ │ │ -A0955 Uncompressed Size 0000B5FA (46586) │ │ │ │ -A0959 Filename Length 001F (31) │ │ │ │ -A095B Extra Length 0018 (24) │ │ │ │ -A095D Comment Length 0000 (0) │ │ │ │ -A095F Disk Start 0000 (0) │ │ │ │ -A0961 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0963 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0967 Local Header Offset 00067E1C (425500) │ │ │ │ -A096B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA096B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A098A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A098C Length 0005 (5) │ │ │ │ -A098E Flags 01 (1) 'Modification' │ │ │ │ -A098F Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0993 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0995 Length 000B (11) │ │ │ │ -A0997 Version 01 (1) │ │ │ │ -A0998 UID Size 04 (4) │ │ │ │ -A0999 UID 00000000 (0) │ │ │ │ -A099D GID Size 04 (4) │ │ │ │ -A099E GID 00000000 (0) │ │ │ │ - │ │ │ │ -A09A2 CENTRAL HEADER #58 02014B50 (33639248) │ │ │ │ -A09A6 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A09A7 Created OS 03 (3) 'Unix' │ │ │ │ -A09A8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A09A9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A09AA General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A09AC Compression Method 0008 (8) 'Deflated' │ │ │ │ -A09AE Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A09B2 CRC 92A69325 (2460390181) │ │ │ │ -A09B6 Compressed Size 00000E81 (3713) │ │ │ │ -A09BA Uncompressed Size 000052DA (21210) │ │ │ │ -A09BE Filename Length 001F (31) │ │ │ │ -A09C0 Extra Length 0018 (24) │ │ │ │ -A09C2 Comment Length 0000 (0) │ │ │ │ -A09C4 Disk Start 0000 (0) │ │ │ │ -A09C6 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A09C8 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A09CC Local Header Offset 0006A30E (434958) │ │ │ │ -A09D0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA09D0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A09EF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A09F1 Length 0005 (5) │ │ │ │ -A09F3 Flags 01 (1) 'Modification' │ │ │ │ -A09F4 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A09F8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A09FA Length 000B (11) │ │ │ │ -A09FC Version 01 (1) │ │ │ │ -A09FD UID Size 04 (4) │ │ │ │ -A09FE UID 00000000 (0) │ │ │ │ -A0A02 GID Size 04 (4) │ │ │ │ -A0A03 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0A07 CENTRAL HEADER #59 02014B50 (33639248) │ │ │ │ -A0A0B Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0A0C Created OS 03 (3) 'Unix' │ │ │ │ -A0A0D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0A0E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0A0F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0A11 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0A13 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0A17 CRC A77B8D6E (2809892206) │ │ │ │ -A0A1B Compressed Size 00000A46 (2630) │ │ │ │ -A0A1F Uncompressed Size 0000247B (9339) │ │ │ │ -A0A23 Filename Length 0013 (19) │ │ │ │ -A0A25 Extra Length 0018 (24) │ │ │ │ -A0A27 Comment Length 0000 (0) │ │ │ │ -A0A29 Disk Start 0000 (0) │ │ │ │ -A0A2B Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0A2D Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0A31 Local Header Offset 0006B1E8 (438760) │ │ │ │ -A0A35 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0A35: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0A48 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0A4A Length 0005 (5) │ │ │ │ -A0A4C Flags 01 (1) 'Modification' │ │ │ │ -A0A4D Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0A51 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0A53 Length 000B (11) │ │ │ │ -A0A55 Version 01 (1) │ │ │ │ -A0A56 UID Size 04 (4) │ │ │ │ -A0A57 UID 00000000 (0) │ │ │ │ -A0A5B GID Size 04 (4) │ │ │ │ -A0A5C GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0A60 CENTRAL HEADER #60 02014B50 (33639248) │ │ │ │ -A0A64 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0A65 Created OS 03 (3) 'Unix' │ │ │ │ -A0A66 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0A67 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0A68 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0A6A Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0A6C Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0A70 CRC 30B97F5F (817463135) │ │ │ │ -A0A74 Compressed Size 00002486 (9350) │ │ │ │ -A0A78 Uncompressed Size 0000B84D (47181) │ │ │ │ -A0A7C Filename Length 0019 (25) │ │ │ │ -A0A7E Extra Length 0018 (24) │ │ │ │ -A0A80 Comment Length 0000 (0) │ │ │ │ -A0A82 Disk Start 0000 (0) │ │ │ │ -A0A84 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0A86 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0A8A Local Header Offset 0006BC7B (441467) │ │ │ │ -A0A8E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0A8E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0AA7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0AA9 Length 0005 (5) │ │ │ │ -A0AAB Flags 01 (1) 'Modification' │ │ │ │ -A0AAC Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0AB0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0AB2 Length 000B (11) │ │ │ │ -A0AB4 Version 01 (1) │ │ │ │ -A0AB5 UID Size 04 (4) │ │ │ │ -A0AB6 UID 00000000 (0) │ │ │ │ -A0ABA GID Size 04 (4) │ │ │ │ -A0ABB GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0ABF CENTRAL HEADER #61 02014B50 (33639248) │ │ │ │ -A0AC3 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0AC4 Created OS 03 (3) 'Unix' │ │ │ │ -A0AC5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0AC6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0AC7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0AC9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0ACB Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0ACF CRC DC97F08B (3700945035) │ │ │ │ -A0AD3 Compressed Size 00000EFB (3835) │ │ │ │ -A0AD7 Uncompressed Size 00003A2D (14893) │ │ │ │ -A0ADB Filename Length 0024 (36) │ │ │ │ -A0ADD Extra Length 0018 (24) │ │ │ │ -A0ADF Comment Length 0000 (0) │ │ │ │ -A0AE1 Disk Start 0000 (0) │ │ │ │ -A0AE3 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0AE5 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0AE9 Local Header Offset 0006E154 (450900) │ │ │ │ -A0AED Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0AED: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0B11 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0B13 Length 0005 (5) │ │ │ │ -A0B15 Flags 01 (1) 'Modification' │ │ │ │ -A0B16 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0B1A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0B1C Length 000B (11) │ │ │ │ -A0B1E Version 01 (1) │ │ │ │ -A0B1F UID Size 04 (4) │ │ │ │ -A0B20 UID 00000000 (0) │ │ │ │ -A0B24 GID Size 04 (4) │ │ │ │ -A0B25 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0B29 CENTRAL HEADER #62 02014B50 (33639248) │ │ │ │ -A0B2D Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0B2E Created OS 03 (3) 'Unix' │ │ │ │ -A0B2F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0B30 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0B31 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0B33 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0B35 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0B39 CRC 396B41A9 (963330473) │ │ │ │ -A0B3D Compressed Size 00001ABA (6842) │ │ │ │ -A0B41 Uncompressed Size 00005F39 (24377) │ │ │ │ -A0B45 Filename Length 0017 (23) │ │ │ │ -A0B47 Extra Length 0018 (24) │ │ │ │ -A0B49 Comment Length 0000 (0) │ │ │ │ -A0B4B Disk Start 0000 (0) │ │ │ │ -A0B4D Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0B4F Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0B53 Local Header Offset 0006F0AD (454829) │ │ │ │ -A0B57 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0B57: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0B6E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0B70 Length 0005 (5) │ │ │ │ -A0B72 Flags 01 (1) 'Modification' │ │ │ │ -A0B73 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0B77 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0B79 Length 000B (11) │ │ │ │ -A0B7B Version 01 (1) │ │ │ │ -A0B7C UID Size 04 (4) │ │ │ │ -A0B7D UID 00000000 (0) │ │ │ │ -A0B81 GID Size 04 (4) │ │ │ │ -A0B82 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0B86 CENTRAL HEADER #63 02014B50 (33639248) │ │ │ │ -A0B8A Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0B8B Created OS 03 (3) 'Unix' │ │ │ │ -A0B8C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0B8D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0B8E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0B90 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0B92 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0B96 CRC 3DD8544C (1037587532) │ │ │ │ -A0B9A Compressed Size 00000ED1 (3793) │ │ │ │ -A0B9E Uncompressed Size 000038DE (14558) │ │ │ │ -A0BA2 Filename Length 0023 (35) │ │ │ │ -A0BA4 Extra Length 0018 (24) │ │ │ │ -A0BA6 Comment Length 0000 (0) │ │ │ │ -A0BA8 Disk Start 0000 (0) │ │ │ │ -A0BAA Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0BAC Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0BB0 Local Header Offset 00070BB8 (461752) │ │ │ │ -A0BB4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0BB4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0BD7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0BD9 Length 0005 (5) │ │ │ │ -A0BDB Flags 01 (1) 'Modification' │ │ │ │ -A0BDC Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0BE0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0BE2 Length 000B (11) │ │ │ │ -A0BE4 Version 01 (1) │ │ │ │ -A0BE5 UID Size 04 (4) │ │ │ │ -A0BE6 UID 00000000 (0) │ │ │ │ -A0BEA GID Size 04 (4) │ │ │ │ -A0BEB GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0BEF CENTRAL HEADER #64 02014B50 (33639248) │ │ │ │ -A0BF3 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0BF4 Created OS 03 (3) 'Unix' │ │ │ │ -A0BF5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0BF6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0BF7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0BF9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0BFB Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0BFF CRC 2DB7929F (767005343) │ │ │ │ -A0C03 Compressed Size 00000113 (275) │ │ │ │ -A0C07 Uncompressed Size 000001F3 (499) │ │ │ │ -A0C0B Filename Length 001B (27) │ │ │ │ -A0C0D Extra Length 0018 (24) │ │ │ │ -A0C0F Comment Length 0000 (0) │ │ │ │ -A0C11 Disk Start 0000 (0) │ │ │ │ -A0C13 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0C15 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0C19 Local Header Offset 00071AE6 (465638) │ │ │ │ -A0C1D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0C1D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0C38 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0C3A Length 0005 (5) │ │ │ │ -A0C3C Flags 01 (1) 'Modification' │ │ │ │ -A0C3D Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0C41 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0C43 Length 000B (11) │ │ │ │ -A0C45 Version 01 (1) │ │ │ │ -A0C46 UID Size 04 (4) │ │ │ │ -A0C47 UID 00000000 (0) │ │ │ │ -A0C4B GID Size 04 (4) │ │ │ │ -A0C4C GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0C50 CENTRAL HEADER #65 02014B50 (33639248) │ │ │ │ -A0C54 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0C55 Created OS 03 (3) 'Unix' │ │ │ │ -A0C56 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0C57 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0C58 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0C5A Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0C5C Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0C60 CRC B802BFBB (3087187899) │ │ │ │ -A0C64 Compressed Size 0000188E (6286) │ │ │ │ -A0C68 Uncompressed Size 00008FAD (36781) │ │ │ │ -A0C6C Filename Length 001D (29) │ │ │ │ -A0C6E Extra Length 0018 (24) │ │ │ │ -A0C70 Comment Length 0000 (0) │ │ │ │ -A0C72 Disk Start 0000 (0) │ │ │ │ -A0C74 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0C76 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0C7A Local Header Offset 00071C4E (465998) │ │ │ │ -A0C7E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0C7E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0C9B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0C9D Length 0005 (5) │ │ │ │ -A0C9F Flags 01 (1) 'Modification' │ │ │ │ -A0CA0 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0CA4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0CA6 Length 000B (11) │ │ │ │ -A0CA8 Version 01 (1) │ │ │ │ -A0CA9 UID Size 04 (4) │ │ │ │ -A0CAA UID 00000000 (0) │ │ │ │ -A0CAE GID Size 04 (4) │ │ │ │ -A0CAF GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0CB3 CENTRAL HEADER #66 02014B50 (33639248) │ │ │ │ -A0CB7 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0CB8 Created OS 03 (3) 'Unix' │ │ │ │ -A0CB9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0CBA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0CBB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0CBD Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0CBF Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0CC3 CRC 516C0F5D (1366036317) │ │ │ │ -A0CC7 Compressed Size 0000164A (5706) │ │ │ │ -A0CCB Uncompressed Size 00003A9C (15004) │ │ │ │ -A0CCF Filename Length 0015 (21) │ │ │ │ -A0CD1 Extra Length 0018 (24) │ │ │ │ -A0CD3 Comment Length 0000 (0) │ │ │ │ -A0CD5 Disk Start 0000 (0) │ │ │ │ -A0CD7 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0CD9 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0CDD Local Header Offset 00073533 (472371) │ │ │ │ -A0CE1 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0CE1: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0CF6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0CF8 Length 0005 (5) │ │ │ │ -A0CFA Flags 01 (1) 'Modification' │ │ │ │ -A0CFB Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0CFF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0D01 Length 000B (11) │ │ │ │ -A0D03 Version 01 (1) │ │ │ │ -A0D04 UID Size 04 (4) │ │ │ │ -A0D05 UID 00000000 (0) │ │ │ │ -A0D09 GID Size 04 (4) │ │ │ │ -A0D0A GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0D0E CENTRAL HEADER #67 02014B50 (33639248) │ │ │ │ -A0D12 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0D13 Created OS 03 (3) 'Unix' │ │ │ │ -A0D14 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0D15 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0D16 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0D18 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0D1A Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0D1E CRC 3708308C (923283596) │ │ │ │ -A0D22 Compressed Size 00003B4E (15182) │ │ │ │ -A0D26 Uncompressed Size 00011CC3 (72899) │ │ │ │ -A0D2A Filename Length 0016 (22) │ │ │ │ -A0D2C Extra Length 0018 (24) │ │ │ │ -A0D2E Comment Length 0000 (0) │ │ │ │ -A0D30 Disk Start 0000 (0) │ │ │ │ -A0D32 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0D34 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0D38 Local Header Offset 00074BCC (478156) │ │ │ │ -A0D3C Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0D3C: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0D52 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0D54 Length 0005 (5) │ │ │ │ -A0D56 Flags 01 (1) 'Modification' │ │ │ │ -A0D57 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0D5B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0D5D Length 000B (11) │ │ │ │ -A0D5F Version 01 (1) │ │ │ │ -A0D60 UID Size 04 (4) │ │ │ │ -A0D61 UID 00000000 (0) │ │ │ │ -A0D65 GID Size 04 (4) │ │ │ │ -A0D66 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0D6A CENTRAL HEADER #68 02014B50 (33639248) │ │ │ │ -A0D6E Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0D6F Created OS 03 (3) 'Unix' │ │ │ │ -A0D70 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0D71 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0D72 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0D74 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0D76 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0D7A CRC 7096BE5F (1888927327) │ │ │ │ -A0D7E Compressed Size 00003EC5 (16069) │ │ │ │ -A0D82 Uncompressed Size 0001C79C (116636) │ │ │ │ -A0D86 Filename Length 0019 (25) │ │ │ │ -A0D88 Extra Length 0018 (24) │ │ │ │ -A0D8A Comment Length 0000 (0) │ │ │ │ -A0D8C Disk Start 0000 (0) │ │ │ │ -A0D8E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0D90 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0D94 Local Header Offset 0007876A (493418) │ │ │ │ -A0D98 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0D98: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0DB1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0DB3 Length 0005 (5) │ │ │ │ -A0DB5 Flags 01 (1) 'Modification' │ │ │ │ -A0DB6 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0DBA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0DBC Length 000B (11) │ │ │ │ -A0DBE Version 01 (1) │ │ │ │ -A0DBF UID Size 04 (4) │ │ │ │ -A0DC0 UID 00000000 (0) │ │ │ │ -A0DC4 GID Size 04 (4) │ │ │ │ -A0DC5 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0DC9 CENTRAL HEADER #69 02014B50 (33639248) │ │ │ │ -A0DCD Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0DCE Created OS 03 (3) 'Unix' │ │ │ │ -A0DCF Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0DD0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0DD1 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0DD3 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0DD5 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0DD9 CRC 9FB04586 (2679129478) │ │ │ │ -A0DDD Compressed Size 00000835 (2101) │ │ │ │ -A0DE1 Uncompressed Size 00003384 (13188) │ │ │ │ -A0DE5 Filename Length 0011 (17) │ │ │ │ -A0DE7 Extra Length 0018 (24) │ │ │ │ -A0DE9 Comment Length 0000 (0) │ │ │ │ -A0DEB Disk Start 0000 (0) │ │ │ │ -A0DED Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0DEF Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0DF3 Local Header Offset 0007C682 (509570) │ │ │ │ -A0DF7 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0DF7: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0E08 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0E0A Length 0005 (5) │ │ │ │ -A0E0C Flags 01 (1) 'Modification' │ │ │ │ -A0E0D Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0E11 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0E13 Length 000B (11) │ │ │ │ -A0E15 Version 01 (1) │ │ │ │ -A0E16 UID Size 04 (4) │ │ │ │ -A0E17 UID 00000000 (0) │ │ │ │ -A0E1B GID Size 04 (4) │ │ │ │ -A0E1C GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0E20 CENTRAL HEADER #70 02014B50 (33639248) │ │ │ │ -A0E24 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0E25 Created OS 03 (3) 'Unix' │ │ │ │ -A0E26 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0E27 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0E28 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0E2A Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0E2C Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0E30 CRC 48157666 (1209366118) │ │ │ │ -A0E34 Compressed Size 000051AB (20907) │ │ │ │ -A0E38 Uncompressed Size 0001FBE0 (130016) │ │ │ │ -A0E3C Filename Length 0015 (21) │ │ │ │ -A0E3E Extra Length 0018 (24) │ │ │ │ -A0E40 Comment Length 0000 (0) │ │ │ │ -A0E42 Disk Start 0000 (0) │ │ │ │ -A0E44 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0E46 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0E4A Local Header Offset 0007CF02 (511746) │ │ │ │ -A0E4E Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0E4E: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0E63 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0E65 Length 0005 (5) │ │ │ │ -A0E67 Flags 01 (1) 'Modification' │ │ │ │ -A0E68 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0E6C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0E6E Length 000B (11) │ │ │ │ -A0E70 Version 01 (1) │ │ │ │ -A0E71 UID Size 04 (4) │ │ │ │ -A0E72 UID 00000000 (0) │ │ │ │ -A0E76 GID Size 04 (4) │ │ │ │ -A0E77 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0E7B CENTRAL HEADER #71 02014B50 (33639248) │ │ │ │ -A0E7F Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0E80 Created OS 03 (3) 'Unix' │ │ │ │ -A0E81 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0E82 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0E83 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0E85 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0E87 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0E8B CRC 11C15F57 (297885527) │ │ │ │ -A0E8F Compressed Size 00001B09 (6921) │ │ │ │ -A0E93 Uncompressed Size 000081D0 (33232) │ │ │ │ -A0E97 Filename Length 0019 (25) │ │ │ │ -A0E99 Extra Length 0018 (24) │ │ │ │ -A0E9B Comment Length 0000 (0) │ │ │ │ -A0E9D Disk Start 0000 (0) │ │ │ │ -A0E9F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0EA1 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0EA5 Local Header Offset 000820FC (532732) │ │ │ │ -A0EA9 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0EA9: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0EC2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0EC4 Length 0005 (5) │ │ │ │ -A0EC6 Flags 01 (1) 'Modification' │ │ │ │ -A0EC7 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0ECB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0ECD Length 000B (11) │ │ │ │ -A0ECF Version 01 (1) │ │ │ │ -A0ED0 UID Size 04 (4) │ │ │ │ -A0ED1 UID 00000000 (0) │ │ │ │ -A0ED5 GID Size 04 (4) │ │ │ │ -A0ED6 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0EDA CENTRAL HEADER #72 02014B50 (33639248) │ │ │ │ -A0EDE Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0EDF Created OS 03 (3) 'Unix' │ │ │ │ -A0EE0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0EE1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0EE2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0EE4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0EE6 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0EEA CRC ECB3AEBD (3971198653) │ │ │ │ -A0EEE Compressed Size 00000D99 (3481) │ │ │ │ -A0EF2 Uncompressed Size 00002EA0 (11936) │ │ │ │ -A0EF6 Filename Length 0018 (24) │ │ │ │ -A0EF8 Extra Length 0018 (24) │ │ │ │ -A0EFA Comment Length 0000 (0) │ │ │ │ -A0EFC Disk Start 0000 (0) │ │ │ │ -A0EFE Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0F00 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0F04 Local Header Offset 00083C58 (539736) │ │ │ │ -A0F08 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0F08: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0F20 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0F22 Length 0005 (5) │ │ │ │ -A0F24 Flags 01 (1) 'Modification' │ │ │ │ -A0F25 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0F29 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0F2B Length 000B (11) │ │ │ │ -A0F2D Version 01 (1) │ │ │ │ -A0F2E UID Size 04 (4) │ │ │ │ -A0F2F UID 00000000 (0) │ │ │ │ -A0F33 GID Size 04 (4) │ │ │ │ -A0F34 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0F38 CENTRAL HEADER #73 02014B50 (33639248) │ │ │ │ -A0F3C Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0F3D Created OS 03 (3) 'Unix' │ │ │ │ -A0F3E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0F3F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0F40 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0F42 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0F44 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0F48 CRC 037E3A9E (58604190) │ │ │ │ -A0F4C Compressed Size 000001E1 (481) │ │ │ │ -A0F50 Uncompressed Size 00000324 (804) │ │ │ │ -A0F54 Filename Length 0011 (17) │ │ │ │ -A0F56 Extra Length 0018 (24) │ │ │ │ -A0F58 Comment Length 0000 (0) │ │ │ │ -A0F5A Disk Start 0000 (0) │ │ │ │ -A0F5C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0F5E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0F62 Local Header Offset 00084A43 (543299) │ │ │ │ -A0F66 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0F66: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0F77 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0F79 Length 0005 (5) │ │ │ │ -A0F7B Flags 01 (1) 'Modification' │ │ │ │ -A0F7C Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0F80 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0F82 Length 000B (11) │ │ │ │ -A0F84 Version 01 (1) │ │ │ │ -A0F85 UID Size 04 (4) │ │ │ │ -A0F86 UID 00000000 (0) │ │ │ │ -A0F8A GID Size 04 (4) │ │ │ │ -A0F8B GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0F8F CENTRAL HEADER #74 02014B50 (33639248) │ │ │ │ -A0F93 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0F94 Created OS 03 (3) 'Unix' │ │ │ │ -A0F95 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0F96 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0F97 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0F99 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0F9B Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0F9F CRC 55187787 (1427666823) │ │ │ │ -A0FA3 Compressed Size 000006C3 (1731) │ │ │ │ -A0FA7 Uncompressed Size 0000143A (5178) │ │ │ │ -A0FAB Filename Length 0019 (25) │ │ │ │ -A0FAD Extra Length 0018 (24) │ │ │ │ -A0FAF Comment Length 0000 (0) │ │ │ │ -A0FB1 Disk Start 0000 (0) │ │ │ │ -A0FB3 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0FB5 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0FB9 Local Header Offset 00084C6F (543855) │ │ │ │ -A0FBD Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0FBD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0FD6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0FD8 Length 0005 (5) │ │ │ │ -A0FDA Flags 01 (1) 'Modification' │ │ │ │ -A0FDB Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0FDF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0FE1 Length 000B (11) │ │ │ │ -A0FE3 Version 01 (1) │ │ │ │ -A0FE4 UID Size 04 (4) │ │ │ │ -A0FE5 UID 00000000 (0) │ │ │ │ -A0FE9 GID Size 04 (4) │ │ │ │ -A0FEA GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0FEE CENTRAL HEADER #75 02014B50 (33639248) │ │ │ │ -A0FF2 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0FF3 Created OS 03 (3) 'Unix' │ │ │ │ -A0FF4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0FF5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0FF6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0FF8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0FFA Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A0FFE CRC B80689A0 (3087436192) │ │ │ │ -A1002 Compressed Size 00001EAE (7854) │ │ │ │ -A1006 Uncompressed Size 0000CA81 (51841) │ │ │ │ -A100A Filename Length 0018 (24) │ │ │ │ -A100C Extra Length 0018 (24) │ │ │ │ -A100E Comment Length 0000 (0) │ │ │ │ -A1010 Disk Start 0000 (0) │ │ │ │ -A1012 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A1014 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A1018 Local Header Offset 00085385 (545669) │ │ │ │ -A101C Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA101C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A1034 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A1036 Length 0005 (5) │ │ │ │ -A1038 Flags 01 (1) 'Modification' │ │ │ │ -A1039 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A103D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A103F Length 000B (11) │ │ │ │ -A1041 Version 01 (1) │ │ │ │ -A1042 UID Size 04 (4) │ │ │ │ -A1043 UID 00000000 (0) │ │ │ │ -A1047 GID Size 04 (4) │ │ │ │ -A1048 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A104C CENTRAL HEADER #76 02014B50 (33639248) │ │ │ │ -A1050 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A1051 Created OS 03 (3) 'Unix' │ │ │ │ -A1052 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A1053 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A1054 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A1056 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A1058 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A105C CRC 70CE7879 (1892579449) │ │ │ │ -A1060 Compressed Size 00001BEA (7146) │ │ │ │ -A1064 Uncompressed Size 0000C5EA (50666) │ │ │ │ -A1068 Filename Length 0012 (18) │ │ │ │ -A106A Extra Length 0018 (24) │ │ │ │ -A106C Comment Length 0000 (0) │ │ │ │ -A106E Disk Start 0000 (0) │ │ │ │ -A1070 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A1072 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A1076 Local Header Offset 00087285 (553605) │ │ │ │ -A107A Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA107A: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A108C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A108E Length 0005 (5) │ │ │ │ -A1090 Flags 01 (1) 'Modification' │ │ │ │ -A1091 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A1095 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A1097 Length 000B (11) │ │ │ │ -A1099 Version 01 (1) │ │ │ │ -A109A UID Size 04 (4) │ │ │ │ -A109B UID 00000000 (0) │ │ │ │ -A109F GID Size 04 (4) │ │ │ │ -A10A0 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A10A4 CENTRAL HEADER #77 02014B50 (33639248) │ │ │ │ -A10A8 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A10A9 Created OS 03 (3) 'Unix' │ │ │ │ -A10AA Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A10AB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A10AC General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A10AE Compression Method 0008 (8) 'Deflated' │ │ │ │ -A10B0 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A10B4 CRC A289EFE2 (2726948834) │ │ │ │ -A10B8 Compressed Size 00001E0F (7695) │ │ │ │ -A10BC Uncompressed Size 00008804 (34820) │ │ │ │ -A10C0 Filename Length 0016 (22) │ │ │ │ -A10C2 Extra Length 0018 (24) │ │ │ │ -A10C4 Comment Length 0000 (0) │ │ │ │ -A10C6 Disk Start 0000 (0) │ │ │ │ -A10C8 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A10CA Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A10CE Local Header Offset 00088EBB (560827) │ │ │ │ -A10D2 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA10D2: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A10E8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A10EA Length 0005 (5) │ │ │ │ -A10EC Flags 01 (1) 'Modification' │ │ │ │ -A10ED Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A10F1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A10F3 Length 000B (11) │ │ │ │ -A10F5 Version 01 (1) │ │ │ │ -A10F6 UID Size 04 (4) │ │ │ │ -A10F7 UID 00000000 (0) │ │ │ │ -A10FB GID Size 04 (4) │ │ │ │ -A10FC GID 00000000 (0) │ │ │ │ - │ │ │ │ -A1100 CENTRAL HEADER #78 02014B50 (33639248) │ │ │ │ -A1104 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A1105 Created OS 03 (3) 'Unix' │ │ │ │ -A1106 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A1107 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A1108 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A110A Compression Method 0008 (8) 'Deflated' │ │ │ │ -A110C Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A1110 CRC A54D678B (2773313419) │ │ │ │ -A1114 Compressed Size 000029A3 (10659) │ │ │ │ -A1118 Uncompressed Size 0000D050 (53328) │ │ │ │ -A111C Filename Length 001A (26) │ │ │ │ -A111E Extra Length 0018 (24) │ │ │ │ -A1120 Comment Length 0000 (0) │ │ │ │ -A1122 Disk Start 0000 (0) │ │ │ │ -A1124 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A1126 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A112A Local Header Offset 0008AD1A (568602) │ │ │ │ -A112E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA112E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A1148 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A114A Length 0005 (5) │ │ │ │ -A114C Flags 01 (1) 'Modification' │ │ │ │ -A114D Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A1151 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A1153 Length 000B (11) │ │ │ │ -A1155 Version 01 (1) │ │ │ │ -A1156 UID Size 04 (4) │ │ │ │ -A1157 UID 00000000 (0) │ │ │ │ -A115B GID Size 04 (4) │ │ │ │ -A115C GID 00000000 (0) │ │ │ │ - │ │ │ │ -A1160 CENTRAL HEADER #79 02014B50 (33639248) │ │ │ │ -A1164 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A1165 Created OS 03 (3) 'Unix' │ │ │ │ -A1166 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A1167 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A1168 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A116A Compression Method 0008 (8) 'Deflated' │ │ │ │ -A116C Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A1170 CRC 80E743AF (2162639791) │ │ │ │ -A1174 Compressed Size 000009AE (2478) │ │ │ │ -A1178 Uncompressed Size 00001DB7 (7607) │ │ │ │ -A117C Filename Length 0018 (24) │ │ │ │ -A117E Extra Length 0018 (24) │ │ │ │ -A1180 Comment Length 0000 (0) │ │ │ │ -A1182 Disk Start 0000 (0) │ │ │ │ -A1184 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A1186 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A118A Local Header Offset 0008D711 (579345) │ │ │ │ -A118E Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA118E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A11A6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A11A8 Length 0005 (5) │ │ │ │ -A11AA Flags 01 (1) 'Modification' │ │ │ │ -A11AB Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A11AF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A11B1 Length 000B (11) │ │ │ │ -A11B3 Version 01 (1) │ │ │ │ -A11B4 UID Size 04 (4) │ │ │ │ -A11B5 UID 00000000 (0) │ │ │ │ -A11B9 GID Size 04 (4) │ │ │ │ -A11BA GID 00000000 (0) │ │ │ │ - │ │ │ │ -A11BE CENTRAL HEADER #80 02014B50 (33639248) │ │ │ │ -A11C2 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A11C3 Created OS 03 (3) 'Unix' │ │ │ │ -A11C4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A11C5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A11C6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A11C8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A11CA Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A11CE CRC F5E2129F (4125233823) │ │ │ │ -A11D2 Compressed Size 000016BC (5820) │ │ │ │ -A11D6 Uncompressed Size 000016CD (5837) │ │ │ │ -A11DA Filename Length 0015 (21) │ │ │ │ -A11DC Extra Length 0018 (24) │ │ │ │ -A11DE Comment Length 0000 (0) │ │ │ │ -A11E0 Disk Start 0000 (0) │ │ │ │ -A11E2 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A11E4 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A11E8 Local Header Offset 0008E111 (581905) │ │ │ │ -A11EC Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA11EC: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A1201 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A1203 Length 0005 (5) │ │ │ │ -A1205 Flags 01 (1) 'Modification' │ │ │ │ -A1206 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A120A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A120C Length 000B (11) │ │ │ │ -A120E Version 01 (1) │ │ │ │ -A120F UID Size 04 (4) │ │ │ │ -A1210 UID 00000000 (0) │ │ │ │ -A1214 GID Size 04 (4) │ │ │ │ -A1215 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A1219 CENTRAL HEADER #81 02014B50 (33639248) │ │ │ │ -A121D Created Zip Spec 3D (61) '6.1' │ │ │ │ -A121E Created OS 03 (3) 'Unix' │ │ │ │ -A121F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A1220 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A1221 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A1223 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A1225 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A1229 CRC F5E2129F (4125233823) │ │ │ │ -A122D Compressed Size 000016BC (5820) │ │ │ │ -A1231 Uncompressed Size 000016CD (5837) │ │ │ │ -A1235 Filename Length 001C (28) │ │ │ │ -A1237 Extra Length 0018 (24) │ │ │ │ -A1239 Comment Length 0000 (0) │ │ │ │ -A123B Disk Start 0000 (0) │ │ │ │ -A123D Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A123F Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A1243 Local Header Offset 0008F81C (587804) │ │ │ │ -A1247 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA1247: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A1263 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A1265 Length 0005 (5) │ │ │ │ -A1267 Flags 01 (1) 'Modification' │ │ │ │ -A1268 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A126C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A126E Length 000B (11) │ │ │ │ -A1270 Version 01 (1) │ │ │ │ -A1271 UID Size 04 (4) │ │ │ │ -A1272 UID 00000000 (0) │ │ │ │ -A1276 GID Size 04 (4) │ │ │ │ -A1277 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A127B CENTRAL HEADER #82 02014B50 (33639248) │ │ │ │ -A127F Created Zip Spec 3D (61) '6.1' │ │ │ │ -A1280 Created OS 03 (3) 'Unix' │ │ │ │ -A1281 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -A1282 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A1283 General Purpose Flag 0000 (0) │ │ │ │ -A1285 Compression Method 0000 (0) 'Stored' │ │ │ │ -A1287 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A128B CRC FC95F24B (4237685323) │ │ │ │ -A128F Compressed Size 00001B84 (7044) │ │ │ │ -A1293 Uncompressed Size 00001B84 (7044) │ │ │ │ -A1297 Filename Length 0016 (22) │ │ │ │ -A1299 Extra Length 0018 (24) │ │ │ │ -A129B Comment Length 0000 (0) │ │ │ │ -A129D Disk Start 0000 (0) │ │ │ │ -A129F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A12A1 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A12A5 Local Header Offset 00090F2E (593710) │ │ │ │ -A12A9 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA12A9: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A12BF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A12C1 Length 0005 (5) │ │ │ │ -A12C3 Flags 01 (1) 'Modification' │ │ │ │ -A12C4 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A12C8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A12CA Length 000B (11) │ │ │ │ -A12CC Version 01 (1) │ │ │ │ -A12CD UID Size 04 (4) │ │ │ │ -A12CE UID 00000000 (0) │ │ │ │ -A12D2 GID Size 04 (4) │ │ │ │ -A12D3 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A12D7 CENTRAL HEADER #83 02014B50 (33639248) │ │ │ │ -A12DB Created Zip Spec 3D (61) '6.1' │ │ │ │ -A12DC Created OS 03 (3) 'Unix' │ │ │ │ -A12DD Extract Zip Spec 0A (10) '1.0' │ │ │ │ -A12DE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A12DF General Purpose Flag 0000 (0) │ │ │ │ -A12E1 Compression Method 0000 (0) 'Stored' │ │ │ │ -A12E3 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A12E7 CRC D0D71F86 (3503759238) │ │ │ │ -A12EB Compressed Size 00000B7B (2939) │ │ │ │ -A12EF Uncompressed Size 00000B7B (2939) │ │ │ │ -A12F3 Filename Length 0016 (22) │ │ │ │ -A12F5 Extra Length 0018 (24) │ │ │ │ -A12F7 Comment Length 0000 (0) │ │ │ │ -A12F9 Disk Start 0000 (0) │ │ │ │ -A12FB Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A12FD Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A1301 Local Header Offset 00092B02 (600834) │ │ │ │ -A1305 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA1305: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A131B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A131D Length 0005 (5) │ │ │ │ -A131F Flags 01 (1) 'Modification' │ │ │ │ -A1320 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A1324 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A1326 Length 000B (11) │ │ │ │ -A1328 Version 01 (1) │ │ │ │ -A1329 UID Size 04 (4) │ │ │ │ -A132A UID 00000000 (0) │ │ │ │ -A132E GID Size 04 (4) │ │ │ │ -A132F GID 00000000 (0) │ │ │ │ - │ │ │ │ -A1333 CENTRAL HEADER #84 02014B50 (33639248) │ │ │ │ -A1337 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A1338 Created OS 03 (3) 'Unix' │ │ │ │ -A1339 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -A133A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A133B General Purpose Flag 0000 (0) │ │ │ │ -A133D Compression Method 0000 (0) 'Stored' │ │ │ │ -A133F Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A1343 CRC FFF9C4D2 (4294558930) │ │ │ │ -A1347 Compressed Size 0000138F (5007) │ │ │ │ -A134B Uncompressed Size 0000138F (5007) │ │ │ │ -A134F Filename Length 0016 (22) │ │ │ │ -A1351 Extra Length 0018 (24) │ │ │ │ -A1353 Comment Length 0000 (0) │ │ │ │ -A1355 Disk Start 0000 (0) │ │ │ │ -A1357 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A1359 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A135D Local Header Offset 000936CD (603853) │ │ │ │ -A1361 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA1361: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A1377 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A1379 Length 0005 (5) │ │ │ │ -A137B Flags 01 (1) 'Modification' │ │ │ │ -A137C Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A1380 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A1382 Length 000B (11) │ │ │ │ -A1384 Version 01 (1) │ │ │ │ -A1385 UID Size 04 (4) │ │ │ │ -A1386 UID 00000000 (0) │ │ │ │ -A138A GID Size 04 (4) │ │ │ │ -A138B GID 00000000 (0) │ │ │ │ - │ │ │ │ -A138F CENTRAL HEADER #85 02014B50 (33639248) │ │ │ │ -A1393 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A1394 Created OS 03 (3) 'Unix' │ │ │ │ -A1395 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -A1396 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A1397 General Purpose Flag 0000 (0) │ │ │ │ -A1399 Compression Method 0000 (0) 'Stored' │ │ │ │ -A139B Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A139F CRC A1037E8E (2701360782) │ │ │ │ -A13A3 Compressed Size 0000145E (5214) │ │ │ │ -A13A7 Uncompressed Size 0000145E (5214) │ │ │ │ -A13AB Filename Length 0016 (22) │ │ │ │ -A13AD Extra Length 0018 (24) │ │ │ │ -A13AF Comment Length 0000 (0) │ │ │ │ -A13B1 Disk Start 0000 (0) │ │ │ │ -A13B3 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A13B5 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A13B9 Local Header Offset 00094AAC (608940) │ │ │ │ -A13BD Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA13BD: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A13D3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A13D5 Length 0005 (5) │ │ │ │ -A13D7 Flags 01 (1) 'Modification' │ │ │ │ -A13D8 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A13DC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A13DE Length 000B (11) │ │ │ │ -A13E0 Version 01 (1) │ │ │ │ -A13E1 UID Size 04 (4) │ │ │ │ -A13E2 UID 00000000 (0) │ │ │ │ -A13E6 GID Size 04 (4) │ │ │ │ -A13E7 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A13EB CENTRAL HEADER #86 02014B50 (33639248) │ │ │ │ -A13EF Created Zip Spec 3D (61) '6.1' │ │ │ │ -A13F0 Created OS 03 (3) 'Unix' │ │ │ │ -A13F1 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -A13F2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A13F3 General Purpose Flag 0000 (0) │ │ │ │ -A13F5 Compression Method 0000 (0) 'Stored' │ │ │ │ -A13F7 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A13FB CRC 5E9E64F1 (1587438833) │ │ │ │ -A13FF Compressed Size 000008EC (2284) │ │ │ │ -A1403 Uncompressed Size 000008EC (2284) │ │ │ │ -A1407 Filename Length 0016 (22) │ │ │ │ -A1409 Extra Length 0018 (24) │ │ │ │ -A140B Comment Length 0000 (0) │ │ │ │ -A140D Disk Start 0000 (0) │ │ │ │ -A140F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A1411 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A1415 Local Header Offset 00095F5A (614234) │ │ │ │ -A1419 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA1419: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A142F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A1431 Length 0005 (5) │ │ │ │ -A1433 Flags 01 (1) 'Modification' │ │ │ │ -A1434 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A1438 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A143A Length 000B (11) │ │ │ │ -A143C Version 01 (1) │ │ │ │ -A143D UID Size 04 (4) │ │ │ │ -A143E UID 00000000 (0) │ │ │ │ -A1442 GID Size 04 (4) │ │ │ │ -A1443 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A1447 CENTRAL HEADER #87 02014B50 (33639248) │ │ │ │ -A144B Created Zip Spec 3D (61) '6.1' │ │ │ │ -A144C Created OS 03 (3) 'Unix' │ │ │ │ -A144D Extract Zip Spec 0A (10) '1.0' │ │ │ │ -A144E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A144F General Purpose Flag 0000 (0) │ │ │ │ -A1451 Compression Method 0000 (0) 'Stored' │ │ │ │ -A1453 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A1457 CRC 42E340AB (1122189483) │ │ │ │ -A145B Compressed Size 00001F2E (7982) │ │ │ │ -A145F Uncompressed Size 00001F2E (7982) │ │ │ │ -A1463 Filename Length 001E (30) │ │ │ │ -A1465 Extra Length 0018 (24) │ │ │ │ -A1467 Comment Length 0000 (0) │ │ │ │ -A1469 Disk Start 0000 (0) │ │ │ │ -A146B Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A146D Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A1471 Local Header Offset 00096896 (616598) │ │ │ │ -A1475 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA1475: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A1493 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A1495 Length 0005 (5) │ │ │ │ -A1497 Flags 01 (1) 'Modification' │ │ │ │ -A1498 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A149C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A149E Length 000B (11) │ │ │ │ -A14A0 Version 01 (1) │ │ │ │ -A14A1 UID Size 04 (4) │ │ │ │ -A14A2 UID 00000000 (0) │ │ │ │ -A14A6 GID Size 04 (4) │ │ │ │ -A14A7 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A14AB CENTRAL HEADER #88 02014B50 (33639248) │ │ │ │ -A14AF Created Zip Spec 3D (61) '6.1' │ │ │ │ -A14B0 Created OS 03 (3) 'Unix' │ │ │ │ -A14B1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A14B2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A14B3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A14B5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A14B7 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A14BB CRC A2C99D83 (2731122051) │ │ │ │ -A14BF Compressed Size 00004096 (16534) │ │ │ │ -A14C3 Uncompressed Size 0001971B (104219) │ │ │ │ -A14C7 Filename Length 001A (26) │ │ │ │ -A14C9 Extra Length 0018 (24) │ │ │ │ -A14CB Comment Length 0000 (0) │ │ │ │ -A14CD Disk Start 0000 (0) │ │ │ │ -A14CF Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A14D1 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A14D5 Local Header Offset 0009881C (624668) │ │ │ │ -A14D9 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA14D9: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A14F3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A14F5 Length 0005 (5) │ │ │ │ -A14F7 Flags 01 (1) 'Modification' │ │ │ │ -A14F8 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A14FC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A14FE Length 000B (11) │ │ │ │ -A1500 Version 01 (1) │ │ │ │ -A1501 UID Size 04 (4) │ │ │ │ -A1502 UID 00000000 (0) │ │ │ │ -A1506 GID Size 04 (4) │ │ │ │ -A1507 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A150B CENTRAL HEADER #89 02014B50 (33639248) │ │ │ │ -A150F Created Zip Spec 3D (61) '6.1' │ │ │ │ -A1510 Created OS 03 (3) 'Unix' │ │ │ │ -A1511 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A1512 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A1513 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A1515 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A1517 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A151B CRC 57F9A5F4 (1475978740) │ │ │ │ -A151F Compressed Size 000029CF (10703) │ │ │ │ -A1523 Uncompressed Size 0000BB3A (47930) │ │ │ │ -A1527 Filename Length 0018 (24) │ │ │ │ -A1529 Extra Length 0018 (24) │ │ │ │ -A152B Comment Length 0000 (0) │ │ │ │ -A152D Disk Start 0000 (0) │ │ │ │ -A152F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A1531 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A1535 Local Header Offset 0009C906 (641286) │ │ │ │ -A1539 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA1539: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A1551 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A1553 Length 0005 (5) │ │ │ │ -A1555 Flags 01 (1) 'Modification' │ │ │ │ -A1556 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A155A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A155C Length 000B (11) │ │ │ │ -A155E Version 01 (1) │ │ │ │ -A155F UID Size 04 (4) │ │ │ │ -A1560 UID 00000000 (0) │ │ │ │ -A1564 GID Size 04 (4) │ │ │ │ -A1565 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A1569 CENTRAL HEADER #90 02014B50 (33639248) │ │ │ │ -A156D Created Zip Spec 3D (61) '6.1' │ │ │ │ -A156E Created OS 03 (3) 'Unix' │ │ │ │ -A156F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A1570 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A1571 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A1573 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A1575 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A1579 CRC DCB3B516 (3702764822) │ │ │ │ -A157D Compressed Size 000000AE (174) │ │ │ │ -A1581 Uncompressed Size 000000FC (252) │ │ │ │ -A1585 Filename Length 0016 (22) │ │ │ │ -A1587 Extra Length 0018 (24) │ │ │ │ -A1589 Comment Length 0000 (0) │ │ │ │ -A158B Disk Start 0000 (0) │ │ │ │ -A158D Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A158F Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A1593 Local Header Offset 0009F327 (652071) │ │ │ │ -A1597 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA1597: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A15AD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A15AF Length 0005 (5) │ │ │ │ -A15B1 Flags 01 (1) 'Modification' │ │ │ │ -A15B2 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A15B6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A15B8 Length 000B (11) │ │ │ │ -A15BA Version 01 (1) │ │ │ │ -A15BB UID Size 04 (4) │ │ │ │ -A15BC UID 00000000 (0) │ │ │ │ -A15C0 GID Size 04 (4) │ │ │ │ -A15C1 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A15C5 CENTRAL HEADER #91 02014B50 (33639248) │ │ │ │ -A15C9 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A15CA Created OS 03 (3) 'Unix' │ │ │ │ -A15CB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A15CC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A15CD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A15CF Compression Method 0008 (8) 'Deflated' │ │ │ │ -A15D1 Modification Time 5CA56E84 (1554345604) 'Tue May 5 13:52:08 2026' │ │ │ │ -A15D5 CRC 58439733 (1480824627) │ │ │ │ -A15D9 Compressed Size 00000077 (119) │ │ │ │ -A15DD Uncompressed Size 000000A2 (162) │ │ │ │ -A15E1 Filename Length 002D (45) │ │ │ │ -A15E3 Extra Length 0018 (24) │ │ │ │ -A15E5 Comment Length 0000 (0) │ │ │ │ -A15E7 Disk Start 0000 (0) │ │ │ │ -A15E9 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A15EB Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A15EF Local Header Offset 0009F425 (652325) │ │ │ │ -A15F3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA15F3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A1620 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A1622 Length 0005 (5) │ │ │ │ -A1624 Flags 01 (1) 'Modification' │ │ │ │ -A1625 Modification Time 69F9F608 (1777989128) 'Tue May 5 13:52:08 2026' │ │ │ │ -A1629 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A162B Length 000B (11) │ │ │ │ -A162D Version 01 (1) │ │ │ │ -A162E UID Size 04 (4) │ │ │ │ -A162F UID 00000000 (0) │ │ │ │ -A1633 GID Size 04 (4) │ │ │ │ -A1634 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A1638 END CENTRAL HEADER 06054B50 (101010256) │ │ │ │ -A163C Number of this disk 0000 (0) │ │ │ │ -A163E Central Dir Disk no 0000 (0) │ │ │ │ -A1640 Entries in this disk 005B (91) │ │ │ │ -A1642 Total Entries 005B (91) │ │ │ │ -A1644 Size of Central Dir 00002135 (8501) │ │ │ │ -A1648 Offset to Central Dir 0009F503 (652547) │ │ │ │ -A164C Comment Length 0000 (0) │ │ │ │ +9F431 LOCAL HEADER #91 04034B50 (67324752) │ │ │ │ +9F435 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F436 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F437 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F439 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F43B Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9F43F CRC 58439733 (1480824627) │ │ │ │ +9F443 Compressed Size 00000077 (119) │ │ │ │ +9F447 Uncompressed Size 000000A2 (162) │ │ │ │ +9F44B Filename Length 002D (45) │ │ │ │ +9F44D Extra Length 001C (28) │ │ │ │ +9F44F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F44F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F47C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F47E Length 0009 (9) │ │ │ │ +9F480 Flags 03 (3) 'Modification Access' │ │ │ │ +9F481 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9F485 Access Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9F489 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F48B Length 000B (11) │ │ │ │ +9F48D Version 01 (1) │ │ │ │ +9F48E UID Size 04 (4) │ │ │ │ +9F48F UID 00000000 (0) │ │ │ │ +9F493 GID Size 04 (4) │ │ │ │ +9F494 GID 00000000 (0) │ │ │ │ +9F498 PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ + │ │ │ │ +9F50F CENTRAL HEADER #1 02014B50 (33639248) │ │ │ │ +9F513 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F514 Created OS 03 (3) 'Unix' │ │ │ │ +9F515 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +9F516 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F517 General Purpose Flag 0000 (0) │ │ │ │ +9F519 Compression Method 0000 (0) 'Stored' │ │ │ │ +9F51B Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9F51F CRC 2CAB616F (749429103) │ │ │ │ +9F523 Compressed Size 00000014 (20) │ │ │ │ +9F527 Uncompressed Size 00000014 (20) │ │ │ │ +9F52B Filename Length 0008 (8) │ │ │ │ +9F52D Extra Length 0018 (24) │ │ │ │ +9F52F Comment Length 0000 (0) │ │ │ │ +9F531 Disk Start 0000 (0) │ │ │ │ +9F533 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F535 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F539 Local Header Offset 00000000 (0) │ │ │ │ +9F53D Filename 'XXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F53D: Filename 'XXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F545 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F547 Length 0005 (5) │ │ │ │ +9F549 Flags 01 (1) 'Modification' │ │ │ │ +9F54A Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9F54E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F550 Length 000B (11) │ │ │ │ +9F552 Version 01 (1) │ │ │ │ +9F553 UID Size 04 (4) │ │ │ │ +9F554 UID 00000000 (0) │ │ │ │ +9F558 GID Size 04 (4) │ │ │ │ +9F559 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F55D CENTRAL HEADER #2 02014B50 (33639248) │ │ │ │ +9F561 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F562 Created OS 03 (3) 'Unix' │ │ │ │ +9F563 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F564 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F565 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F567 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F569 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9F56D CRC 7CFCE72A (2096949034) │ │ │ │ +9F571 Compressed Size 000015AD (5549) │ │ │ │ +9F575 Uncompressed Size 00004603 (17923) │ │ │ │ +9F579 Filename Length 0014 (20) │ │ │ │ +9F57B Extra Length 0018 (24) │ │ │ │ +9F57D Comment Length 0000 (0) │ │ │ │ +9F57F Disk Start 0000 (0) │ │ │ │ +9F581 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F583 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F587 Local Header Offset 00000056 (86) │ │ │ │ +9F58B Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F58B: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F59F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F5A1 Length 0005 (5) │ │ │ │ +9F5A3 Flags 01 (1) 'Modification' │ │ │ │ +9F5A4 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9F5A8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F5AA Length 000B (11) │ │ │ │ +9F5AC Version 01 (1) │ │ │ │ +9F5AD UID Size 04 (4) │ │ │ │ +9F5AE UID 00000000 (0) │ │ │ │ +9F5B2 GID Size 04 (4) │ │ │ │ +9F5B3 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F5B7 CENTRAL HEADER #3 02014B50 (33639248) │ │ │ │ +9F5BB Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F5BC Created OS 03 (3) 'Unix' │ │ │ │ +9F5BD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F5BE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F5BF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F5C1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F5C3 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9F5C7 CRC 741FDE60 (1948245600) │ │ │ │ +9F5CB Compressed Size 000006D6 (1750) │ │ │ │ +9F5CF Uncompressed Size 00001242 (4674) │ │ │ │ +9F5D3 Filename Length 0013 (19) │ │ │ │ +9F5D5 Extra Length 0018 (24) │ │ │ │ +9F5D7 Comment Length 0000 (0) │ │ │ │ +9F5D9 Disk Start 0000 (0) │ │ │ │ +9F5DB Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F5DD Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F5E1 Local Header Offset 00001651 (5713) │ │ │ │ +9F5E5 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F5E5: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F5F8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F5FA Length 0005 (5) │ │ │ │ +9F5FC Flags 01 (1) 'Modification' │ │ │ │ +9F5FD Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9F601 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F603 Length 000B (11) │ │ │ │ +9F605 Version 01 (1) │ │ │ │ +9F606 UID Size 04 (4) │ │ │ │ +9F607 UID 00000000 (0) │ │ │ │ +9F60B GID Size 04 (4) │ │ │ │ +9F60C GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F610 CENTRAL HEADER #4 02014B50 (33639248) │ │ │ │ +9F614 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F615 Created OS 03 (3) 'Unix' │ │ │ │ +9F616 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F617 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F618 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F61A Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F61C Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9F620 CRC 19F7DFEB (435675115) │ │ │ │ +9F624 Compressed Size 00002DA3 (11683) │ │ │ │ +9F628 Uncompressed Size 0000D0C0 (53440) │ │ │ │ +9F62C Filename Length 0014 (20) │ │ │ │ +9F62E Extra Length 0018 (24) │ │ │ │ +9F630 Comment Length 0000 (0) │ │ │ │ +9F632 Disk Start 0000 (0) │ │ │ │ +9F634 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F636 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F63A Local Header Offset 00001D74 (7540) │ │ │ │ +9F63E Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F63E: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F652 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F654 Length 0005 (5) │ │ │ │ +9F656 Flags 01 (1) 'Modification' │ │ │ │ +9F657 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9F65B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F65D Length 000B (11) │ │ │ │ +9F65F Version 01 (1) │ │ │ │ +9F660 UID Size 04 (4) │ │ │ │ +9F661 UID 00000000 (0) │ │ │ │ +9F665 GID Size 04 (4) │ │ │ │ +9F666 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F66A CENTRAL HEADER #5 02014B50 (33639248) │ │ │ │ +9F66E Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F66F Created OS 03 (3) 'Unix' │ │ │ │ +9F670 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F671 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F672 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F674 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F676 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9F67A CRC AFEB34EA (2951427306) │ │ │ │ +9F67E Compressed Size 000003F0 (1008) │ │ │ │ +9F682 Uncompressed Size 00000877 (2167) │ │ │ │ +9F686 Filename Length 0014 (20) │ │ │ │ +9F688 Extra Length 0018 (24) │ │ │ │ +9F68A Comment Length 0000 (0) │ │ │ │ +9F68C Disk Start 0000 (0) │ │ │ │ +9F68E Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F690 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F694 Local Header Offset 00004B65 (19301) │ │ │ │ +9F698 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F698: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F6AC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F6AE Length 0005 (5) │ │ │ │ +9F6B0 Flags 01 (1) 'Modification' │ │ │ │ +9F6B1 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9F6B5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F6B7 Length 000B (11) │ │ │ │ +9F6B9 Version 01 (1) │ │ │ │ +9F6BA UID Size 04 (4) │ │ │ │ +9F6BB UID 00000000 (0) │ │ │ │ +9F6BF GID Size 04 (4) │ │ │ │ +9F6C0 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F6C4 CENTRAL HEADER #6 02014B50 (33639248) │ │ │ │ +9F6C8 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F6C9 Created OS 03 (3) 'Unix' │ │ │ │ +9F6CA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F6CB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F6CC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F6CE Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F6D0 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9F6D4 CRC 21AF85A7 (565151143) │ │ │ │ +9F6D8 Compressed Size 000001AE (430) │ │ │ │ +9F6DC Uncompressed Size 000002FE (766) │ │ │ │ +9F6E0 Filename Length 0011 (17) │ │ │ │ +9F6E2 Extra Length 0018 (24) │ │ │ │ +9F6E4 Comment Length 0000 (0) │ │ │ │ +9F6E6 Disk Start 0000 (0) │ │ │ │ +9F6E8 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F6EA Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F6EE Local Header Offset 00004FA3 (20387) │ │ │ │ +9F6F2 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F6F2: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F703 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F705 Length 0005 (5) │ │ │ │ +9F707 Flags 01 (1) 'Modification' │ │ │ │ +9F708 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9F70C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F70E Length 000B (11) │ │ │ │ +9F710 Version 01 (1) │ │ │ │ +9F711 UID Size 04 (4) │ │ │ │ +9F712 UID 00000000 (0) │ │ │ │ +9F716 GID Size 04 (4) │ │ │ │ +9F717 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F71B CENTRAL HEADER #7 02014B50 (33639248) │ │ │ │ +9F71F Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F720 Created OS 03 (3) 'Unix' │ │ │ │ +9F721 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F722 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F723 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F725 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F727 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9F72B CRC 9BD5D9C7 (2614483399) │ │ │ │ +9F72F Compressed Size 000020C5 (8389) │ │ │ │ +9F733 Uncompressed Size 0000B4B1 (46257) │ │ │ │ +9F737 Filename Length 001B (27) │ │ │ │ +9F739 Extra Length 0018 (24) │ │ │ │ +9F73B Comment Length 0000 (0) │ │ │ │ +9F73D Disk Start 0000 (0) │ │ │ │ +9F73F Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F741 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F745 Local Header Offset 0000519C (20892) │ │ │ │ +9F749 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F749: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F764 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F766 Length 0005 (5) │ │ │ │ +9F768 Flags 01 (1) 'Modification' │ │ │ │ +9F769 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9F76D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F76F Length 000B (11) │ │ │ │ +9F771 Version 01 (1) │ │ │ │ +9F772 UID Size 04 (4) │ │ │ │ +9F773 UID 00000000 (0) │ │ │ │ +9F777 GID Size 04 (4) │ │ │ │ +9F778 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F77C CENTRAL HEADER #8 02014B50 (33639248) │ │ │ │ +9F780 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F781 Created OS 03 (3) 'Unix' │ │ │ │ +9F782 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F783 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F784 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F786 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F788 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9F78C CRC 2D371F1E (758587166) │ │ │ │ +9F790 Compressed Size 00000E70 (3696) │ │ │ │ +9F794 Uncompressed Size 000030B3 (12467) │ │ │ │ +9F798 Filename Length 001D (29) │ │ │ │ +9F79A Extra Length 0018 (24) │ │ │ │ +9F79C Comment Length 0000 (0) │ │ │ │ +9F79E Disk Start 0000 (0) │ │ │ │ +9F7A0 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F7A2 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F7A6 Local Header Offset 000072B6 (29366) │ │ │ │ +9F7AA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F7AA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F7C7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F7C9 Length 0005 (5) │ │ │ │ +9F7CB Flags 01 (1) 'Modification' │ │ │ │ +9F7CC Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9F7D0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F7D2 Length 000B (11) │ │ │ │ +9F7D4 Version 01 (1) │ │ │ │ +9F7D5 UID Size 04 (4) │ │ │ │ +9F7D6 UID 00000000 (0) │ │ │ │ +9F7DA GID Size 04 (4) │ │ │ │ +9F7DB GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F7DF CENTRAL HEADER #9 02014B50 (33639248) │ │ │ │ +9F7E3 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F7E4 Created OS 03 (3) 'Unix' │ │ │ │ +9F7E5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F7E6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F7E7 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F7E9 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F7EB Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9F7EF CRC 7903D0CB (2030293195) │ │ │ │ +9F7F3 Compressed Size 00000973 (2419) │ │ │ │ +9F7F7 Uncompressed Size 00001CB3 (7347) │ │ │ │ +9F7FB Filename Length 0019 (25) │ │ │ │ +9F7FD Extra Length 0018 (24) │ │ │ │ +9F7FF Comment Length 0000 (0) │ │ │ │ +9F801 Disk Start 0000 (0) │ │ │ │ +9F803 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F805 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F809 Local Header Offset 0000817D (33149) │ │ │ │ +9F80D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F80D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F826 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F828 Length 0005 (5) │ │ │ │ +9F82A Flags 01 (1) 'Modification' │ │ │ │ +9F82B Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9F82F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F831 Length 000B (11) │ │ │ │ +9F833 Version 01 (1) │ │ │ │ +9F834 UID Size 04 (4) │ │ │ │ +9F835 UID 00000000 (0) │ │ │ │ +9F839 GID Size 04 (4) │ │ │ │ +9F83A GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F83E CENTRAL HEADER #10 02014B50 (33639248) │ │ │ │ +9F842 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F843 Created OS 03 (3) 'Unix' │ │ │ │ +9F844 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F845 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F846 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F848 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F84A Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9F84E CRC 3959782C (962164780) │ │ │ │ +9F852 Compressed Size 00003880 (14464) │ │ │ │ +9F856 Uncompressed Size 0000F7F5 (63477) │ │ │ │ +9F85A Filename Length 0015 (21) │ │ │ │ +9F85C Extra Length 0018 (24) │ │ │ │ +9F85E Comment Length 0000 (0) │ │ │ │ +9F860 Disk Start 0000 (0) │ │ │ │ +9F862 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F864 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F868 Local Header Offset 00008B43 (35651) │ │ │ │ +9F86C Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F86C: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F881 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F883 Length 0005 (5) │ │ │ │ +9F885 Flags 01 (1) 'Modification' │ │ │ │ +9F886 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9F88A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F88C Length 000B (11) │ │ │ │ +9F88E Version 01 (1) │ │ │ │ +9F88F UID Size 04 (4) │ │ │ │ +9F890 UID 00000000 (0) │ │ │ │ +9F894 GID Size 04 (4) │ │ │ │ +9F895 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F899 CENTRAL HEADER #11 02014B50 (33639248) │ │ │ │ +9F89D Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F89E Created OS 03 (3) 'Unix' │ │ │ │ +9F89F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F8A0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F8A1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F8A3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F8A5 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9F8A9 CRC 430C8466 (1124893798) │ │ │ │ +9F8AD Compressed Size 0000AB09 (43785) │ │ │ │ +9F8B1 Uncompressed Size 0003E052 (254034) │ │ │ │ +9F8B5 Filename Length 0012 (18) │ │ │ │ +9F8B7 Extra Length 0018 (24) │ │ │ │ +9F8B9 Comment Length 0000 (0) │ │ │ │ +9F8BB Disk Start 0000 (0) │ │ │ │ +9F8BD Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F8BF Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F8C3 Local Header Offset 0000C412 (50194) │ │ │ │ +9F8C7 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F8C7: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F8D9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F8DB Length 0005 (5) │ │ │ │ +9F8DD Flags 01 (1) 'Modification' │ │ │ │ +9F8DE Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9F8E2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F8E4 Length 000B (11) │ │ │ │ +9F8E6 Version 01 (1) │ │ │ │ +9F8E7 UID Size 04 (4) │ │ │ │ +9F8E8 UID 00000000 (0) │ │ │ │ +9F8EC GID Size 04 (4) │ │ │ │ +9F8ED GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F8F1 CENTRAL HEADER #12 02014B50 (33639248) │ │ │ │ +9F8F5 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F8F6 Created OS 03 (3) 'Unix' │ │ │ │ +9F8F7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F8F8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F8F9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F8FB Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F8FD Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9F901 CRC 1EC6C3B8 (516342712) │ │ │ │ +9F905 Compressed Size 00003B0F (15119) │ │ │ │ +9F909 Uncompressed Size 0001B46D (111725) │ │ │ │ +9F90D Filename Length 0015 (21) │ │ │ │ +9F90F Extra Length 0018 (24) │ │ │ │ +9F911 Comment Length 0000 (0) │ │ │ │ +9F913 Disk Start 0000 (0) │ │ │ │ +9F915 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F917 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F91B Local Header Offset 00016F67 (94055) │ │ │ │ +9F91F Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F91F: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F934 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F936 Length 0005 (5) │ │ │ │ +9F938 Flags 01 (1) 'Modification' │ │ │ │ +9F939 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9F93D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F93F Length 000B (11) │ │ │ │ +9F941 Version 01 (1) │ │ │ │ +9F942 UID Size 04 (4) │ │ │ │ +9F943 UID 00000000 (0) │ │ │ │ +9F947 GID Size 04 (4) │ │ │ │ +9F948 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F94C CENTRAL HEADER #13 02014B50 (33639248) │ │ │ │ +9F950 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F951 Created OS 03 (3) 'Unix' │ │ │ │ +9F952 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F953 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F954 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F956 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F958 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9F95C CRC AE44C2B4 (2923741876) │ │ │ │ +9F960 Compressed Size 000091C5 (37317) │ │ │ │ +9F964 Uncompressed Size 0003D9DE (252382) │ │ │ │ +9F968 Filename Length 0014 (20) │ │ │ │ +9F96A Extra Length 0018 (24) │ │ │ │ +9F96C Comment Length 0000 (0) │ │ │ │ +9F96E Disk Start 0000 (0) │ │ │ │ +9F970 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F972 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F976 Local Header Offset 0001AAC5 (109253) │ │ │ │ +9F97A Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F97A: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F98E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F990 Length 0005 (5) │ │ │ │ +9F992 Flags 01 (1) 'Modification' │ │ │ │ +9F993 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9F997 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F999 Length 000B (11) │ │ │ │ +9F99B Version 01 (1) │ │ │ │ +9F99C UID Size 04 (4) │ │ │ │ +9F99D UID 00000000 (0) │ │ │ │ +9F9A1 GID Size 04 (4) │ │ │ │ +9F9A2 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F9A6 CENTRAL HEADER #14 02014B50 (33639248) │ │ │ │ +9F9AA Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F9AB Created OS 03 (3) 'Unix' │ │ │ │ +9F9AC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F9AD Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F9AE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F9B0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F9B2 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9F9B6 CRC 0F5A0761 (257558369) │ │ │ │ +9F9BA Compressed Size 00002A69 (10857) │ │ │ │ +9F9BE Uncompressed Size 00011520 (70944) │ │ │ │ +9F9C2 Filename Length 0016 (22) │ │ │ │ +9F9C4 Extra Length 0018 (24) │ │ │ │ +9F9C6 Comment Length 0000 (0) │ │ │ │ +9F9C8 Disk Start 0000 (0) │ │ │ │ +9F9CA Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F9CC Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F9D0 Local Header Offset 00023CD8 (146648) │ │ │ │ +9F9D4 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F9D4: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F9EA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F9EC Length 0005 (5) │ │ │ │ +9F9EE Flags 01 (1) 'Modification' │ │ │ │ +9F9EF Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9F9F3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F9F5 Length 000B (11) │ │ │ │ +9F9F7 Version 01 (1) │ │ │ │ +9F9F8 UID Size 04 (4) │ │ │ │ +9F9F9 UID 00000000 (0) │ │ │ │ +9F9FD GID Size 04 (4) │ │ │ │ +9F9FE GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FA02 CENTRAL HEADER #15 02014B50 (33639248) │ │ │ │ +9FA06 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FA07 Created OS 03 (3) 'Unix' │ │ │ │ +9FA08 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FA09 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FA0A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FA0C Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FA0E Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9FA12 CRC A70066B2 (2801821362) │ │ │ │ +9FA16 Compressed Size 000014D7 (5335) │ │ │ │ +9FA1A Uncompressed Size 0000518E (20878) │ │ │ │ +9FA1E Filename Length 001D (29) │ │ │ │ +9FA20 Extra Length 0018 (24) │ │ │ │ +9FA22 Comment Length 0000 (0) │ │ │ │ +9FA24 Disk Start 0000 (0) │ │ │ │ +9FA26 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FA28 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FA2C Local Header Offset 00026791 (157585) │ │ │ │ +9FA30 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FA30: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FA4D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FA4F Length 0005 (5) │ │ │ │ +9FA51 Flags 01 (1) 'Modification' │ │ │ │ +9FA52 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9FA56 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FA58 Length 000B (11) │ │ │ │ +9FA5A Version 01 (1) │ │ │ │ +9FA5B UID Size 04 (4) │ │ │ │ +9FA5C UID 00000000 (0) │ │ │ │ +9FA60 GID Size 04 (4) │ │ │ │ +9FA61 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FA65 CENTRAL HEADER #16 02014B50 (33639248) │ │ │ │ +9FA69 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FA6A Created OS 03 (3) 'Unix' │ │ │ │ +9FA6B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FA6C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FA6D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FA6F Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FA71 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9FA75 CRC 82F97239 (2197385785) │ │ │ │ +9FA79 Compressed Size 000039AB (14763) │ │ │ │ +9FA7D Uncompressed Size 0000F080 (61568) │ │ │ │ +9FA81 Filename Length 001C (28) │ │ │ │ +9FA83 Extra Length 0018 (24) │ │ │ │ +9FA85 Comment Length 0000 (0) │ │ │ │ +9FA87 Disk Start 0000 (0) │ │ │ │ +9FA89 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FA8B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FA8F Local Header Offset 00027CBF (163007) │ │ │ │ +9FA93 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FA93: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FAAF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FAB1 Length 0005 (5) │ │ │ │ +9FAB3 Flags 01 (1) 'Modification' │ │ │ │ +9FAB4 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9FAB8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FABA Length 000B (11) │ │ │ │ +9FABC Version 01 (1) │ │ │ │ +9FABD UID Size 04 (4) │ │ │ │ +9FABE UID 00000000 (0) │ │ │ │ +9FAC2 GID Size 04 (4) │ │ │ │ +9FAC3 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FAC7 CENTRAL HEADER #17 02014B50 (33639248) │ │ │ │ +9FACB Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FACC Created OS 03 (3) 'Unix' │ │ │ │ +9FACD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FACE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FACF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FAD1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FAD3 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9FAD7 CRC 7A88ED58 (2055794008) │ │ │ │ +9FADB Compressed Size 000006A3 (1699) │ │ │ │ +9FADF Uncompressed Size 000011F5 (4597) │ │ │ │ +9FAE3 Filename Length 001C (28) │ │ │ │ +9FAE5 Extra Length 0018 (24) │ │ │ │ +9FAE7 Comment Length 0000 (0) │ │ │ │ +9FAE9 Disk Start 0000 (0) │ │ │ │ +9FAEB Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FAED Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FAF1 Local Header Offset 0002B6C0 (177856) │ │ │ │ +9FAF5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FAF5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FB11 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FB13 Length 0005 (5) │ │ │ │ +9FB15 Flags 01 (1) 'Modification' │ │ │ │ +9FB16 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9FB1A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FB1C Length 000B (11) │ │ │ │ +9FB1E Version 01 (1) │ │ │ │ +9FB1F UID Size 04 (4) │ │ │ │ +9FB20 UID 00000000 (0) │ │ │ │ +9FB24 GID Size 04 (4) │ │ │ │ +9FB25 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FB29 CENTRAL HEADER #18 02014B50 (33639248) │ │ │ │ +9FB2D Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FB2E Created OS 03 (3) 'Unix' │ │ │ │ +9FB2F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FB30 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FB31 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FB33 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FB35 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9FB39 CRC F5E61E1F (4125498911) │ │ │ │ +9FB3D Compressed Size 00001082 (4226) │ │ │ │ +9FB41 Uncompressed Size 00004C00 (19456) │ │ │ │ +9FB45 Filename Length 001B (27) │ │ │ │ +9FB47 Extra Length 0018 (24) │ │ │ │ +9FB49 Comment Length 0000 (0) │ │ │ │ +9FB4B Disk Start 0000 (0) │ │ │ │ +9FB4D Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FB4F Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FB53 Local Header Offset 0002BDB9 (179641) │ │ │ │ +9FB57 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FB57: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FB72 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FB74 Length 0005 (5) │ │ │ │ +9FB76 Flags 01 (1) 'Modification' │ │ │ │ +9FB77 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9FB7B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FB7D Length 000B (11) │ │ │ │ +9FB7F Version 01 (1) │ │ │ │ +9FB80 UID Size 04 (4) │ │ │ │ +9FB81 UID 00000000 (0) │ │ │ │ +9FB85 GID Size 04 (4) │ │ │ │ +9FB86 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FB8A CENTRAL HEADER #19 02014B50 (33639248) │ │ │ │ +9FB8E Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FB8F Created OS 03 (3) 'Unix' │ │ │ │ +9FB90 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FB91 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FB92 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FB94 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FB96 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9FB9A CRC 4CF55DF8 (1291148792) │ │ │ │ +9FB9E Compressed Size 000033AB (13227) │ │ │ │ +9FBA2 Uncompressed Size 0000BC95 (48277) │ │ │ │ +9FBA6 Filename Length 001D (29) │ │ │ │ +9FBA8 Extra Length 0018 (24) │ │ │ │ +9FBAA Comment Length 0000 (0) │ │ │ │ +9FBAC Disk Start 0000 (0) │ │ │ │ +9FBAE Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FBB0 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FBB4 Local Header Offset 0002CE90 (183952) │ │ │ │ +9FBB8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FBB8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FBD5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FBD7 Length 0005 (5) │ │ │ │ +9FBD9 Flags 01 (1) 'Modification' │ │ │ │ +9FBDA Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9FBDE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FBE0 Length 000B (11) │ │ │ │ +9FBE2 Version 01 (1) │ │ │ │ +9FBE3 UID Size 04 (4) │ │ │ │ +9FBE4 UID 00000000 (0) │ │ │ │ +9FBE8 GID Size 04 (4) │ │ │ │ +9FBE9 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FBED CENTRAL HEADER #20 02014B50 (33639248) │ │ │ │ +9FBF1 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FBF2 Created OS 03 (3) 'Unix' │ │ │ │ +9FBF3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FBF4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FBF5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FBF7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FBF9 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9FBFD CRC 797D5C21 (2038258721) │ │ │ │ +9FC01 Compressed Size 00000D6A (3434) │ │ │ │ +9FC05 Uncompressed Size 0000388E (14478) │ │ │ │ +9FC09 Filename Length 001D (29) │ │ │ │ +9FC0B Extra Length 0018 (24) │ │ │ │ +9FC0D Comment Length 0000 (0) │ │ │ │ +9FC0F Disk Start 0000 (0) │ │ │ │ +9FC11 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FC13 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FC17 Local Header Offset 00030292 (197266) │ │ │ │ +9FC1B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FC1B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FC38 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FC3A Length 0005 (5) │ │ │ │ +9FC3C Flags 01 (1) 'Modification' │ │ │ │ +9FC3D Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9FC41 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FC43 Length 000B (11) │ │ │ │ +9FC45 Version 01 (1) │ │ │ │ +9FC46 UID Size 04 (4) │ │ │ │ +9FC47 UID 00000000 (0) │ │ │ │ +9FC4B GID Size 04 (4) │ │ │ │ +9FC4C GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FC50 CENTRAL HEADER #21 02014B50 (33639248) │ │ │ │ +9FC54 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FC55 Created OS 03 (3) 'Unix' │ │ │ │ +9FC56 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FC57 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FC58 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FC5A Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FC5C Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9FC60 CRC 20784D7F (544755071) │ │ │ │ +9FC64 Compressed Size 00001C6A (7274) │ │ │ │ +9FC68 Uncompressed Size 0000C187 (49543) │ │ │ │ +9FC6C Filename Length 001A (26) │ │ │ │ +9FC6E Extra Length 0018 (24) │ │ │ │ +9FC70 Comment Length 0000 (0) │ │ │ │ +9FC72 Disk Start 0000 (0) │ │ │ │ +9FC74 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FC76 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FC7A Local Header Offset 00031053 (200787) │ │ │ │ +9FC7E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FC7E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FC98 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FC9A Length 0005 (5) │ │ │ │ +9FC9C Flags 01 (1) 'Modification' │ │ │ │ +9FC9D Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9FCA1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FCA3 Length 000B (11) │ │ │ │ +9FCA5 Version 01 (1) │ │ │ │ +9FCA6 UID Size 04 (4) │ │ │ │ +9FCA7 UID 00000000 (0) │ │ │ │ +9FCAB GID Size 04 (4) │ │ │ │ +9FCAC GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FCB0 CENTRAL HEADER #22 02014B50 (33639248) │ │ │ │ +9FCB4 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FCB5 Created OS 03 (3) 'Unix' │ │ │ │ +9FCB6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FCB7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FCB8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FCBA Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FCBC Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9FCC0 CRC C41B95CB (3290142155) │ │ │ │ +9FCC4 Compressed Size 000003A4 (932) │ │ │ │ +9FCC8 Uncompressed Size 0000088F (2191) │ │ │ │ +9FCCC Filename Length 0012 (18) │ │ │ │ +9FCCE Extra Length 0018 (24) │ │ │ │ +9FCD0 Comment Length 0000 (0) │ │ │ │ +9FCD2 Disk Start 0000 (0) │ │ │ │ +9FCD4 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FCD6 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FCDA Local Header Offset 00032D11 (208145) │ │ │ │ +9FCDE Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FCDE: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FCF0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FCF2 Length 0005 (5) │ │ │ │ +9FCF4 Flags 01 (1) 'Modification' │ │ │ │ +9FCF5 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9FCF9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FCFB Length 000B (11) │ │ │ │ +9FCFD Version 01 (1) │ │ │ │ +9FCFE UID Size 04 (4) │ │ │ │ +9FCFF UID 00000000 (0) │ │ │ │ +9FD03 GID Size 04 (4) │ │ │ │ +9FD04 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FD08 CENTRAL HEADER #23 02014B50 (33639248) │ │ │ │ +9FD0C Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FD0D Created OS 03 (3) 'Unix' │ │ │ │ +9FD0E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FD0F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FD10 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FD12 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FD14 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9FD18 CRC 40B68220 (1085702688) │ │ │ │ +9FD1C Compressed Size 000001D4 (468) │ │ │ │ +9FD20 Uncompressed Size 00000312 (786) │ │ │ │ +9FD24 Filename Length 0020 (32) │ │ │ │ +9FD26 Extra Length 0018 (24) │ │ │ │ +9FD28 Comment Length 0000 (0) │ │ │ │ +9FD2A Disk Start 0000 (0) │ │ │ │ +9FD2C Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FD2E Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FD32 Local Header Offset 00033101 (209153) │ │ │ │ +9FD36 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FD36: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FD56 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FD58 Length 0005 (5) │ │ │ │ +9FD5A Flags 01 (1) 'Modification' │ │ │ │ +9FD5B Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9FD5F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FD61 Length 000B (11) │ │ │ │ +9FD63 Version 01 (1) │ │ │ │ +9FD64 UID Size 04 (4) │ │ │ │ +9FD65 UID 00000000 (0) │ │ │ │ +9FD69 GID Size 04 (4) │ │ │ │ +9FD6A GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FD6E CENTRAL HEADER #24 02014B50 (33639248) │ │ │ │ +9FD72 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FD73 Created OS 03 (3) 'Unix' │ │ │ │ +9FD74 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FD75 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FD76 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FD78 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FD7A Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9FD7E CRC DA6644FF (3664135423) │ │ │ │ +9FD82 Compressed Size 000017AA (6058) │ │ │ │ +9FD86 Uncompressed Size 00009D19 (40217) │ │ │ │ +9FD8A Filename Length 001B (27) │ │ │ │ +9FD8C Extra Length 0018 (24) │ │ │ │ +9FD8E Comment Length 0000 (0) │ │ │ │ +9FD90 Disk Start 0000 (0) │ │ │ │ +9FD92 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FD94 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FD98 Local Header Offset 0003332F (209711) │ │ │ │ +9FD9C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FD9C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FDB7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FDB9 Length 0005 (5) │ │ │ │ +9FDBB Flags 01 (1) 'Modification' │ │ │ │ +9FDBC Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9FDC0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FDC2 Length 000B (11) │ │ │ │ +9FDC4 Version 01 (1) │ │ │ │ +9FDC5 UID Size 04 (4) │ │ │ │ +9FDC6 UID 00000000 (0) │ │ │ │ +9FDCA GID Size 04 (4) │ │ │ │ +9FDCB GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FDCF CENTRAL HEADER #25 02014B50 (33639248) │ │ │ │ +9FDD3 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FDD4 Created OS 03 (3) 'Unix' │ │ │ │ +9FDD5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FDD6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FDD7 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FDD9 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FDDB Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9FDDF CRC 8B976C32 (2341956658) │ │ │ │ +9FDE3 Compressed Size 00001374 (4980) │ │ │ │ +9FDE7 Uncompressed Size 00003B67 (15207) │ │ │ │ +9FDEB Filename Length 0015 (21) │ │ │ │ +9FDED Extra Length 0018 (24) │ │ │ │ +9FDEF Comment Length 0000 (0) │ │ │ │ +9FDF1 Disk Start 0000 (0) │ │ │ │ +9FDF3 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FDF5 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FDF9 Local Header Offset 00034B2E (215854) │ │ │ │ +9FDFD Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FDFD: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FE12 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FE14 Length 0005 (5) │ │ │ │ +9FE16 Flags 01 (1) 'Modification' │ │ │ │ +9FE17 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9FE1B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FE1D Length 000B (11) │ │ │ │ +9FE1F Version 01 (1) │ │ │ │ +9FE20 UID Size 04 (4) │ │ │ │ +9FE21 UID 00000000 (0) │ │ │ │ +9FE25 GID Size 04 (4) │ │ │ │ +9FE26 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FE2A CENTRAL HEADER #26 02014B50 (33639248) │ │ │ │ +9FE2E Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FE2F Created OS 03 (3) 'Unix' │ │ │ │ +9FE30 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FE31 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FE32 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FE34 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FE36 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9FE3A CRC 2ACCA91C (718055708) │ │ │ │ +9FE3E Compressed Size 00000AD3 (2771) │ │ │ │ +9FE42 Uncompressed Size 00002136 (8502) │ │ │ │ +9FE46 Filename Length 0011 (17) │ │ │ │ +9FE48 Extra Length 0018 (24) │ │ │ │ +9FE4A Comment Length 0000 (0) │ │ │ │ +9FE4C Disk Start 0000 (0) │ │ │ │ +9FE4E Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FE50 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FE54 Local Header Offset 00035EF1 (220913) │ │ │ │ +9FE58 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FE58: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FE69 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FE6B Length 0005 (5) │ │ │ │ +9FE6D Flags 01 (1) 'Modification' │ │ │ │ +9FE6E Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9FE72 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FE74 Length 000B (11) │ │ │ │ +9FE76 Version 01 (1) │ │ │ │ +9FE77 UID Size 04 (4) │ │ │ │ +9FE78 UID 00000000 (0) │ │ │ │ +9FE7C GID Size 04 (4) │ │ │ │ +9FE7D GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FE81 CENTRAL HEADER #27 02014B50 (33639248) │ │ │ │ +9FE85 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FE86 Created OS 03 (3) 'Unix' │ │ │ │ +9FE87 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FE88 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FE89 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FE8B Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FE8D Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9FE91 CRC 323952E0 (842617568) │ │ │ │ +9FE95 Compressed Size 000003FE (1022) │ │ │ │ +9FE99 Uncompressed Size 00000F0D (3853) │ │ │ │ +9FE9D Filename Length 0014 (20) │ │ │ │ +9FE9F Extra Length 0018 (24) │ │ │ │ +9FEA1 Comment Length 0000 (0) │ │ │ │ +9FEA3 Disk Start 0000 (0) │ │ │ │ +9FEA5 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FEA7 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FEAB Local Header Offset 00036A0F (223759) │ │ │ │ +9FEAF Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FEAF: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FEC3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FEC5 Length 0005 (5) │ │ │ │ +9FEC7 Flags 01 (1) 'Modification' │ │ │ │ +9FEC8 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9FECC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FECE Length 000B (11) │ │ │ │ +9FED0 Version 01 (1) │ │ │ │ +9FED1 UID Size 04 (4) │ │ │ │ +9FED2 UID 00000000 (0) │ │ │ │ +9FED6 GID Size 04 (4) │ │ │ │ +9FED7 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FEDB CENTRAL HEADER #28 02014B50 (33639248) │ │ │ │ +9FEDF Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FEE0 Created OS 03 (3) 'Unix' │ │ │ │ +9FEE1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FEE2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FEE3 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FEE5 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FEE7 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9FEEB CRC F2ED2F43 (4075630403) │ │ │ │ +9FEEF Compressed Size 00001263 (4707) │ │ │ │ +9FEF3 Uncompressed Size 0000346A (13418) │ │ │ │ +9FEF7 Filename Length 0014 (20) │ │ │ │ +9FEF9 Extra Length 0018 (24) │ │ │ │ +9FEFB Comment Length 0000 (0) │ │ │ │ +9FEFD Disk Start 0000 (0) │ │ │ │ +9FEFF Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FF01 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FF05 Local Header Offset 00036E5B (224859) │ │ │ │ +9FF09 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FF09: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FF1D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FF1F Length 0005 (5) │ │ │ │ +9FF21 Flags 01 (1) 'Modification' │ │ │ │ +9FF22 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9FF26 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FF28 Length 000B (11) │ │ │ │ +9FF2A Version 01 (1) │ │ │ │ +9FF2B UID Size 04 (4) │ │ │ │ +9FF2C UID 00000000 (0) │ │ │ │ +9FF30 GID Size 04 (4) │ │ │ │ +9FF31 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FF35 CENTRAL HEADER #29 02014B50 (33639248) │ │ │ │ +9FF39 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FF3A Created OS 03 (3) 'Unix' │ │ │ │ +9FF3B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FF3C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FF3D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FF3F Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FF41 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9FF45 CRC AA8B7F74 (2861268852) │ │ │ │ +9FF49 Compressed Size 00000AD1 (2769) │ │ │ │ +9FF4D Uncompressed Size 00002300 (8960) │ │ │ │ +9FF51 Filename Length 001B (27) │ │ │ │ +9FF53 Extra Length 0018 (24) │ │ │ │ +9FF55 Comment Length 0000 (0) │ │ │ │ +9FF57 Disk Start 0000 (0) │ │ │ │ +9FF59 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FF5B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FF5F Local Header Offset 0003810C (229644) │ │ │ │ +9FF63 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FF63: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FF7E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FF80 Length 0005 (5) │ │ │ │ +9FF82 Flags 01 (1) 'Modification' │ │ │ │ +9FF83 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9FF87 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FF89 Length 000B (11) │ │ │ │ +9FF8B Version 01 (1) │ │ │ │ +9FF8C UID Size 04 (4) │ │ │ │ +9FF8D UID 00000000 (0) │ │ │ │ +9FF91 GID Size 04 (4) │ │ │ │ +9FF92 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FF96 CENTRAL HEADER #30 02014B50 (33639248) │ │ │ │ +9FF9A Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FF9B Created OS 03 (3) 'Unix' │ │ │ │ +9FF9C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FF9D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FF9E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FFA0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FFA2 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9FFA6 CRC AB095EFA (2869518074) │ │ │ │ +9FFAA Compressed Size 00000A8E (2702) │ │ │ │ +9FFAE Uncompressed Size 0000237B (9083) │ │ │ │ +9FFB2 Filename Length 0013 (19) │ │ │ │ +9FFB4 Extra Length 0018 (24) │ │ │ │ +9FFB6 Comment Length 0000 (0) │ │ │ │ +9FFB8 Disk Start 0000 (0) │ │ │ │ +9FFBA Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FFBC Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FFC0 Local Header Offset 00038C32 (232498) │ │ │ │ +9FFC4 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FFC4: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FFD7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FFD9 Length 0005 (5) │ │ │ │ +9FFDB Flags 01 (1) 'Modification' │ │ │ │ +9FFDC Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +9FFE0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FFE2 Length 000B (11) │ │ │ │ +9FFE4 Version 01 (1) │ │ │ │ +9FFE5 UID Size 04 (4) │ │ │ │ +9FFE6 UID 00000000 (0) │ │ │ │ +9FFEA GID Size 04 (4) │ │ │ │ +9FFEB GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FFEF CENTRAL HEADER #31 02014B50 (33639248) │ │ │ │ +9FFF3 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FFF4 Created OS 03 (3) 'Unix' │ │ │ │ +9FFF5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FFF6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FFF7 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FFF9 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FFFB Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +9FFFF CRC DFB4FE0C (3753180684) │ │ │ │ +A0003 Compressed Size 000010E7 (4327) │ │ │ │ +A0007 Uncompressed Size 00005611 (22033) │ │ │ │ +A000B Filename Length 000F (15) │ │ │ │ +A000D Extra Length 0018 (24) │ │ │ │ +A000F Comment Length 0000 (0) │ │ │ │ +A0011 Disk Start 0000 (0) │ │ │ │ +A0013 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0015 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0019 Local Header Offset 0003970D (235277) │ │ │ │ +A001D Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA001D: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A002C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A002E Length 0005 (5) │ │ │ │ +A0030 Flags 01 (1) 'Modification' │ │ │ │ +A0031 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0035 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0037 Length 000B (11) │ │ │ │ +A0039 Version 01 (1) │ │ │ │ +A003A UID Size 04 (4) │ │ │ │ +A003B UID 00000000 (0) │ │ │ │ +A003F GID Size 04 (4) │ │ │ │ +A0040 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0044 CENTRAL HEADER #32 02014B50 (33639248) │ │ │ │ +A0048 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0049 Created OS 03 (3) 'Unix' │ │ │ │ +A004A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A004B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A004C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A004E Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0050 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0054 CRC C70CE575 (3339511157) │ │ │ │ +A0058 Compressed Size 0000066B (1643) │ │ │ │ +A005C Uncompressed Size 000018E0 (6368) │ │ │ │ +A0060 Filename Length 000F (15) │ │ │ │ +A0062 Extra Length 0018 (24) │ │ │ │ +A0064 Comment Length 0000 (0) │ │ │ │ +A0066 Disk Start 0000 (0) │ │ │ │ +A0068 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A006A Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A006E Local Header Offset 0003A83D (239677) │ │ │ │ +A0072 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0072: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0081 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0083 Length 0005 (5) │ │ │ │ +A0085 Flags 01 (1) 'Modification' │ │ │ │ +A0086 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A008A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A008C Length 000B (11) │ │ │ │ +A008E Version 01 (1) │ │ │ │ +A008F UID Size 04 (4) │ │ │ │ +A0090 UID 00000000 (0) │ │ │ │ +A0094 GID Size 04 (4) │ │ │ │ +A0095 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0099 CENTRAL HEADER #33 02014B50 (33639248) │ │ │ │ +A009D Created Zip Spec 3D (61) '6.1' │ │ │ │ +A009E Created OS 03 (3) 'Unix' │ │ │ │ +A009F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A00A0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A00A1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A00A3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A00A5 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A00A9 CRC 1194DF58 (294969176) │ │ │ │ +A00AD Compressed Size 00001A46 (6726) │ │ │ │ +A00B1 Uncompressed Size 000064F3 (25843) │ │ │ │ +A00B5 Filename Length 0013 (19) │ │ │ │ +A00B7 Extra Length 0018 (24) │ │ │ │ +A00B9 Comment Length 0000 (0) │ │ │ │ +A00BB Disk Start 0000 (0) │ │ │ │ +A00BD Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A00BF Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A00C3 Local Header Offset 0003AEF1 (241393) │ │ │ │ +A00C7 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA00C7: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A00DA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A00DC Length 0005 (5) │ │ │ │ +A00DE Flags 01 (1) 'Modification' │ │ │ │ +A00DF Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A00E3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A00E5 Length 000B (11) │ │ │ │ +A00E7 Version 01 (1) │ │ │ │ +A00E8 UID Size 04 (4) │ │ │ │ +A00E9 UID 00000000 (0) │ │ │ │ +A00ED GID Size 04 (4) │ │ │ │ +A00EE GID 00000000 (0) │ │ │ │ + │ │ │ │ +A00F2 CENTRAL HEADER #34 02014B50 (33639248) │ │ │ │ +A00F6 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A00F7 Created OS 03 (3) 'Unix' │ │ │ │ +A00F8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A00F9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A00FA General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A00FC Compression Method 0008 (8) 'Deflated' │ │ │ │ +A00FE Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0102 CRC BA8C2020 (3129745440) │ │ │ │ +A0106 Compressed Size 000009A7 (2471) │ │ │ │ +A010A Uncompressed Size 00001B65 (7013) │ │ │ │ +A010E Filename Length 0010 (16) │ │ │ │ +A0110 Extra Length 0018 (24) │ │ │ │ +A0112 Comment Length 0000 (0) │ │ │ │ +A0114 Disk Start 0000 (0) │ │ │ │ +A0116 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0118 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A011C Local Header Offset 0003C984 (248196) │ │ │ │ +A0120 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0120: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0130 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0132 Length 0005 (5) │ │ │ │ +A0134 Flags 01 (1) 'Modification' │ │ │ │ +A0135 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0139 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A013B Length 000B (11) │ │ │ │ +A013D Version 01 (1) │ │ │ │ +A013E UID Size 04 (4) │ │ │ │ +A013F UID 00000000 (0) │ │ │ │ +A0143 GID Size 04 (4) │ │ │ │ +A0144 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0148 CENTRAL HEADER #35 02014B50 (33639248) │ │ │ │ +A014C Created Zip Spec 3D (61) '6.1' │ │ │ │ +A014D Created OS 03 (3) 'Unix' │ │ │ │ +A014E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A014F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0150 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0152 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0154 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0158 CRC 61869F50 (1636212560) │ │ │ │ +A015C Compressed Size 000006B9 (1721) │ │ │ │ +A0160 Uncompressed Size 00001566 (5478) │ │ │ │ +A0164 Filename Length 0012 (18) │ │ │ │ +A0166 Extra Length 0018 (24) │ │ │ │ +A0168 Comment Length 0000 (0) │ │ │ │ +A016A Disk Start 0000 (0) │ │ │ │ +A016C Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A016E Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0172 Local Header Offset 0003D375 (250741) │ │ │ │ +A0176 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0176: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0188 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A018A Length 0005 (5) │ │ │ │ +A018C Flags 01 (1) 'Modification' │ │ │ │ +A018D Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0191 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0193 Length 000B (11) │ │ │ │ +A0195 Version 01 (1) │ │ │ │ +A0196 UID Size 04 (4) │ │ │ │ +A0197 UID 00000000 (0) │ │ │ │ +A019B GID Size 04 (4) │ │ │ │ +A019C GID 00000000 (0) │ │ │ │ + │ │ │ │ +A01A0 CENTRAL HEADER #36 02014B50 (33639248) │ │ │ │ +A01A4 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A01A5 Created OS 03 (3) 'Unix' │ │ │ │ +A01A6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A01A7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A01A8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A01AA Compression Method 0008 (8) 'Deflated' │ │ │ │ +A01AC Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A01B0 CRC D4742EBB (3564383931) │ │ │ │ +A01B4 Compressed Size 00002A14 (10772) │ │ │ │ +A01B8 Uncompressed Size 0000B1DD (45533) │ │ │ │ +A01BC Filename Length 0010 (16) │ │ │ │ +A01BE Extra Length 0018 (24) │ │ │ │ +A01C0 Comment Length 0000 (0) │ │ │ │ +A01C2 Disk Start 0000 (0) │ │ │ │ +A01C4 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A01C6 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A01CA Local Header Offset 0003DA7A (252538) │ │ │ │ +A01CE Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA01CE: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A01DE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A01E0 Length 0005 (5) │ │ │ │ +A01E2 Flags 01 (1) 'Modification' │ │ │ │ +A01E3 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A01E7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A01E9 Length 000B (11) │ │ │ │ +A01EB Version 01 (1) │ │ │ │ +A01EC UID Size 04 (4) │ │ │ │ +A01ED UID 00000000 (0) │ │ │ │ +A01F1 GID Size 04 (4) │ │ │ │ +A01F2 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A01F6 CENTRAL HEADER #37 02014B50 (33639248) │ │ │ │ +A01FA Created Zip Spec 3D (61) '6.1' │ │ │ │ +A01FB Created OS 03 (3) 'Unix' │ │ │ │ +A01FC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A01FD Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A01FE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0200 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0202 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0206 CRC 9CCBB559 (2630595929) │ │ │ │ +A020A Compressed Size 00001E8A (7818) │ │ │ │ +A020E Uncompressed Size 00009AAB (39595) │ │ │ │ +A0212 Filename Length 0012 (18) │ │ │ │ +A0214 Extra Length 0018 (24) │ │ │ │ +A0216 Comment Length 0000 (0) │ │ │ │ +A0218 Disk Start 0000 (0) │ │ │ │ +A021A Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A021C Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0220 Local Header Offset 000404D8 (263384) │ │ │ │ +A0224 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0224: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0236 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0238 Length 0005 (5) │ │ │ │ +A023A Flags 01 (1) 'Modification' │ │ │ │ +A023B Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A023F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0241 Length 000B (11) │ │ │ │ +A0243 Version 01 (1) │ │ │ │ +A0244 UID Size 04 (4) │ │ │ │ +A0245 UID 00000000 (0) │ │ │ │ +A0249 GID Size 04 (4) │ │ │ │ +A024A GID 00000000 (0) │ │ │ │ + │ │ │ │ +A024E CENTRAL HEADER #38 02014B50 (33639248) │ │ │ │ +A0252 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0253 Created OS 03 (3) 'Unix' │ │ │ │ +A0254 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0255 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0256 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0258 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A025A Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A025E CRC 2CAA1116 (749342998) │ │ │ │ +A0262 Compressed Size 0000147D (5245) │ │ │ │ +A0266 Uncompressed Size 00007AD0 (31440) │ │ │ │ +A026A Filename Length 0018 (24) │ │ │ │ +A026C Extra Length 0018 (24) │ │ │ │ +A026E Comment Length 0000 (0) │ │ │ │ +A0270 Disk Start 0000 (0) │ │ │ │ +A0272 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0274 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0278 Local Header Offset 000423AE (271278) │ │ │ │ +A027C Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA027C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0294 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0296 Length 0005 (5) │ │ │ │ +A0298 Flags 01 (1) 'Modification' │ │ │ │ +A0299 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A029D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A029F Length 000B (11) │ │ │ │ +A02A1 Version 01 (1) │ │ │ │ +A02A2 UID Size 04 (4) │ │ │ │ +A02A3 UID 00000000 (0) │ │ │ │ +A02A7 GID Size 04 (4) │ │ │ │ +A02A8 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A02AC CENTRAL HEADER #39 02014B50 (33639248) │ │ │ │ +A02B0 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A02B1 Created OS 03 (3) 'Unix' │ │ │ │ +A02B2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A02B3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A02B4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A02B6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A02B8 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A02BC CRC B893F0A2 (3096703138) │ │ │ │ +A02C0 Compressed Size 000018D7 (6359) │ │ │ │ +A02C4 Uncompressed Size 0000A83A (43066) │ │ │ │ +A02C8 Filename Length 001F (31) │ │ │ │ +A02CA Extra Length 0018 (24) │ │ │ │ +A02CC Comment Length 0000 (0) │ │ │ │ +A02CE Disk Start 0000 (0) │ │ │ │ +A02D0 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A02D2 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A02D6 Local Header Offset 0004387D (276605) │ │ │ │ +A02DA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA02DA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A02F9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A02FB Length 0005 (5) │ │ │ │ +A02FD Flags 01 (1) 'Modification' │ │ │ │ +A02FE Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0302 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0304 Length 000B (11) │ │ │ │ +A0306 Version 01 (1) │ │ │ │ +A0307 UID Size 04 (4) │ │ │ │ +A0308 UID 00000000 (0) │ │ │ │ +A030C GID Size 04 (4) │ │ │ │ +A030D GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0311 CENTRAL HEADER #40 02014B50 (33639248) │ │ │ │ +A0315 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0316 Created OS 03 (3) 'Unix' │ │ │ │ +A0317 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0318 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0319 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A031B Compression Method 0008 (8) 'Deflated' │ │ │ │ +A031D Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0321 CRC 8E4EBF9E (2387525534) │ │ │ │ +A0325 Compressed Size 000003F8 (1016) │ │ │ │ +A0329 Uncompressed Size 000008A4 (2212) │ │ │ │ +A032D Filename Length 001E (30) │ │ │ │ +A032F Extra Length 0018 (24) │ │ │ │ +A0331 Comment Length 0000 (0) │ │ │ │ +A0333 Disk Start 0000 (0) │ │ │ │ +A0335 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0337 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A033B Local Header Offset 000451AD (283053) │ │ │ │ +A033F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA033F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A035D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A035F Length 0005 (5) │ │ │ │ +A0361 Flags 01 (1) 'Modification' │ │ │ │ +A0362 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0366 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0368 Length 000B (11) │ │ │ │ +A036A Version 01 (1) │ │ │ │ +A036B UID Size 04 (4) │ │ │ │ +A036C UID 00000000 (0) │ │ │ │ +A0370 GID Size 04 (4) │ │ │ │ +A0371 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0375 CENTRAL HEADER #41 02014B50 (33639248) │ │ │ │ +A0379 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A037A Created OS 03 (3) 'Unix' │ │ │ │ +A037B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A037C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A037D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A037F Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0381 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0385 CRC D9FD2FF3 (3657248755) │ │ │ │ +A0389 Compressed Size 00004296 (17046) │ │ │ │ +A038D Uncompressed Size 0000D8E8 (55528) │ │ │ │ +A0391 Filename Length 0013 (19) │ │ │ │ +A0393 Extra Length 0018 (24) │ │ │ │ +A0395 Comment Length 0000 (0) │ │ │ │ +A0397 Disk Start 0000 (0) │ │ │ │ +A0399 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A039B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A039F Local Header Offset 000455FD (284157) │ │ │ │ +A03A3 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA03A3: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A03B6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A03B8 Length 0005 (5) │ │ │ │ +A03BA Flags 01 (1) 'Modification' │ │ │ │ +A03BB Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A03BF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A03C1 Length 000B (11) │ │ │ │ +A03C3 Version 01 (1) │ │ │ │ +A03C4 UID Size 04 (4) │ │ │ │ +A03C5 UID 00000000 (0) │ │ │ │ +A03C9 GID Size 04 (4) │ │ │ │ +A03CA GID 00000000 (0) │ │ │ │ + │ │ │ │ +A03CE CENTRAL HEADER #42 02014B50 (33639248) │ │ │ │ +A03D2 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A03D3 Created OS 03 (3) 'Unix' │ │ │ │ +A03D4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A03D5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A03D6 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A03D8 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A03DA Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A03DE CRC 33D65333 (869684019) │ │ │ │ +A03E2 Compressed Size 000026C4 (9924) │ │ │ │ +A03E6 Uncompressed Size 00006E46 (28230) │ │ │ │ +A03EA Filename Length 0019 (25) │ │ │ │ +A03EC Extra Length 0018 (24) │ │ │ │ +A03EE Comment Length 0000 (0) │ │ │ │ +A03F0 Disk Start 0000 (0) │ │ │ │ +A03F2 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A03F4 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A03F8 Local Header Offset 000498E0 (301280) │ │ │ │ +A03FC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA03FC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0415 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0417 Length 0005 (5) │ │ │ │ +A0419 Flags 01 (1) 'Modification' │ │ │ │ +A041A Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A041E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0420 Length 000B (11) │ │ │ │ +A0422 Version 01 (1) │ │ │ │ +A0423 UID Size 04 (4) │ │ │ │ +A0424 UID 00000000 (0) │ │ │ │ +A0428 GID Size 04 (4) │ │ │ │ +A0429 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A042D CENTRAL HEADER #43 02014B50 (33639248) │ │ │ │ +A0431 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0432 Created OS 03 (3) 'Unix' │ │ │ │ +A0433 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0434 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0435 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0437 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0439 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A043D CRC A84F7DF4 (2823781876) │ │ │ │ +A0441 Compressed Size 0000273B (10043) │ │ │ │ +A0445 Uncompressed Size 00008B84 (35716) │ │ │ │ +A0449 Filename Length 0019 (25) │ │ │ │ +A044B Extra Length 0018 (24) │ │ │ │ +A044D Comment Length 0000 (0) │ │ │ │ +A044F Disk Start 0000 (0) │ │ │ │ +A0451 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0453 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0457 Local Header Offset 0004BFF7 (311287) │ │ │ │ +A045B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA045B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0474 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0476 Length 0005 (5) │ │ │ │ +A0478 Flags 01 (1) 'Modification' │ │ │ │ +A0479 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A047D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A047F Length 000B (11) │ │ │ │ +A0481 Version 01 (1) │ │ │ │ +A0482 UID Size 04 (4) │ │ │ │ +A0483 UID 00000000 (0) │ │ │ │ +A0487 GID Size 04 (4) │ │ │ │ +A0488 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A048C CENTRAL HEADER #44 02014B50 (33639248) │ │ │ │ +A0490 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0491 Created OS 03 (3) 'Unix' │ │ │ │ +A0492 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0493 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0494 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0496 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0498 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A049C CRC 3C47A4AF (1011328175) │ │ │ │ +A04A0 Compressed Size 00000CF2 (3314) │ │ │ │ +A04A4 Uncompressed Size 0000517B (20859) │ │ │ │ +A04A8 Filename Length 0021 (33) │ │ │ │ +A04AA Extra Length 0018 (24) │ │ │ │ +A04AC Comment Length 0000 (0) │ │ │ │ +A04AE Disk Start 0000 (0) │ │ │ │ +A04B0 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A04B2 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A04B6 Local Header Offset 0004E785 (321413) │ │ │ │ +A04BA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA04BA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A04DB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A04DD Length 0005 (5) │ │ │ │ +A04DF Flags 01 (1) 'Modification' │ │ │ │ +A04E0 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A04E4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A04E6 Length 000B (11) │ │ │ │ +A04E8 Version 01 (1) │ │ │ │ +A04E9 UID Size 04 (4) │ │ │ │ +A04EA UID 00000000 (0) │ │ │ │ +A04EE GID Size 04 (4) │ │ │ │ +A04EF GID 00000000 (0) │ │ │ │ + │ │ │ │ +A04F3 CENTRAL HEADER #45 02014B50 (33639248) │ │ │ │ +A04F7 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A04F8 Created OS 03 (3) 'Unix' │ │ │ │ +A04F9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A04FA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A04FB General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A04FD Compression Method 0008 (8) 'Deflated' │ │ │ │ +A04FF Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0503 CRC 4527F9F0 (1160247792) │ │ │ │ +A0507 Compressed Size 00000469 (1129) │ │ │ │ +A050B Uncompressed Size 00000932 (2354) │ │ │ │ +A050F Filename Length 001B (27) │ │ │ │ +A0511 Extra Length 0018 (24) │ │ │ │ +A0513 Comment Length 0000 (0) │ │ │ │ +A0515 Disk Start 0000 (0) │ │ │ │ +A0517 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0519 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A051D Local Header Offset 0004F4D2 (324818) │ │ │ │ +A0521 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0521: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A053C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A053E Length 0005 (5) │ │ │ │ +A0540 Flags 01 (1) 'Modification' │ │ │ │ +A0541 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0545 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0547 Length 000B (11) │ │ │ │ +A0549 Version 01 (1) │ │ │ │ +A054A UID Size 04 (4) │ │ │ │ +A054B UID 00000000 (0) │ │ │ │ +A054F GID Size 04 (4) │ │ │ │ +A0550 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0554 CENTRAL HEADER #46 02014B50 (33639248) │ │ │ │ +A0558 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0559 Created OS 03 (3) 'Unix' │ │ │ │ +A055A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A055B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A055C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A055E Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0560 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0564 CRC A2B6650C (2729862412) │ │ │ │ +A0568 Compressed Size 000016ED (5869) │ │ │ │ +A056C Uncompressed Size 00007A6E (31342) │ │ │ │ +A0570 Filename Length 001F (31) │ │ │ │ +A0572 Extra Length 0018 (24) │ │ │ │ +A0574 Comment Length 0000 (0) │ │ │ │ +A0576 Disk Start 0000 (0) │ │ │ │ +A0578 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A057A Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A057E Local Header Offset 0004F990 (326032) │ │ │ │ +A0582 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0582: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A05A1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A05A3 Length 0005 (5) │ │ │ │ +A05A5 Flags 01 (1) 'Modification' │ │ │ │ +A05A6 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A05AA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A05AC Length 000B (11) │ │ │ │ +A05AE Version 01 (1) │ │ │ │ +A05AF UID Size 04 (4) │ │ │ │ +A05B0 UID 00000000 (0) │ │ │ │ +A05B4 GID Size 04 (4) │ │ │ │ +A05B5 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A05B9 CENTRAL HEADER #47 02014B50 (33639248) │ │ │ │ +A05BD Created Zip Spec 3D (61) '6.1' │ │ │ │ +A05BE Created OS 03 (3) 'Unix' │ │ │ │ +A05BF Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A05C0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A05C1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A05C3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A05C5 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A05C9 CRC 06C4AFCE (113553358) │ │ │ │ +A05CD Compressed Size 00004166 (16742) │ │ │ │ +A05D1 Uncompressed Size 0001D160 (119136) │ │ │ │ +A05D5 Filename Length 0010 (16) │ │ │ │ +A05D7 Extra Length 0018 (24) │ │ │ │ +A05D9 Comment Length 0000 (0) │ │ │ │ +A05DB Disk Start 0000 (0) │ │ │ │ +A05DD Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A05DF Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A05E3 Local Header Offset 000510D6 (331990) │ │ │ │ +A05E7 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA05E7: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A05F7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A05F9 Length 0005 (5) │ │ │ │ +A05FB Flags 01 (1) 'Modification' │ │ │ │ +A05FC Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0600 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0602 Length 000B (11) │ │ │ │ +A0604 Version 01 (1) │ │ │ │ +A0605 UID Size 04 (4) │ │ │ │ +A0606 UID 00000000 (0) │ │ │ │ +A060A GID Size 04 (4) │ │ │ │ +A060B GID 00000000 (0) │ │ │ │ + │ │ │ │ +A060F CENTRAL HEADER #48 02014B50 (33639248) │ │ │ │ +A0613 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0614 Created OS 03 (3) 'Unix' │ │ │ │ +A0615 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0616 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0617 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0619 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A061B Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A061F CRC 1C486A07 (474507783) │ │ │ │ +A0623 Compressed Size 00000A98 (2712) │ │ │ │ +A0627 Uncompressed Size 00002106 (8454) │ │ │ │ +A062B Filename Length 0014 (20) │ │ │ │ +A062D Extra Length 0018 (24) │ │ │ │ +A062F Comment Length 0000 (0) │ │ │ │ +A0631 Disk Start 0000 (0) │ │ │ │ +A0633 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0635 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0639 Local Header Offset 00055286 (348806) │ │ │ │ +A063D Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA063D: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0651 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0653 Length 0005 (5) │ │ │ │ +A0655 Flags 01 (1) 'Modification' │ │ │ │ +A0656 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A065A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A065C Length 000B (11) │ │ │ │ +A065E Version 01 (1) │ │ │ │ +A065F UID Size 04 (4) │ │ │ │ +A0660 UID 00000000 (0) │ │ │ │ +A0664 GID Size 04 (4) │ │ │ │ +A0665 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0669 CENTRAL HEADER #49 02014B50 (33639248) │ │ │ │ +A066D Created Zip Spec 3D (61) '6.1' │ │ │ │ +A066E Created OS 03 (3) 'Unix' │ │ │ │ +A066F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0670 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0671 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0673 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0675 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0679 CRC A5D82A4E (2782407246) │ │ │ │ +A067D Compressed Size 0000AD70 (44400) │ │ │ │ +A0681 Uncompressed Size 0003EB1B (256795) │ │ │ │ +A0685 Filename Length 0017 (23) │ │ │ │ +A0687 Extra Length 0018 (24) │ │ │ │ +A0689 Comment Length 0000 (0) │ │ │ │ +A068B Disk Start 0000 (0) │ │ │ │ +A068D Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A068F Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0693 Local Header Offset 00055D6C (351596) │ │ │ │ +A0697 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0697: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A06AE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A06B0 Length 0005 (5) │ │ │ │ +A06B2 Flags 01 (1) 'Modification' │ │ │ │ +A06B3 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A06B7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A06B9 Length 000B (11) │ │ │ │ +A06BB Version 01 (1) │ │ │ │ +A06BC UID Size 04 (4) │ │ │ │ +A06BD UID 00000000 (0) │ │ │ │ +A06C1 GID Size 04 (4) │ │ │ │ +A06C2 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A06C6 CENTRAL HEADER #50 02014B50 (33639248) │ │ │ │ +A06CA Created Zip Spec 3D (61) '6.1' │ │ │ │ +A06CB Created OS 03 (3) 'Unix' │ │ │ │ +A06CC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A06CD Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A06CE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A06D0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A06D2 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A06D6 CRC 4F9531C5 (1335177669) │ │ │ │ +A06DA Compressed Size 00000462 (1122) │ │ │ │ +A06DE Uncompressed Size 00000DF4 (3572) │ │ │ │ +A06E2 Filename Length 0013 (19) │ │ │ │ +A06E4 Extra Length 0018 (24) │ │ │ │ +A06E6 Comment Length 0000 (0) │ │ │ │ +A06E8 Disk Start 0000 (0) │ │ │ │ +A06EA Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A06EC Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A06F0 Local Header Offset 00060B2D (396077) │ │ │ │ +A06F4 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA06F4: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0707 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0709 Length 0005 (5) │ │ │ │ +A070B Flags 01 (1) 'Modification' │ │ │ │ +A070C Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0710 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0712 Length 000B (11) │ │ │ │ +A0714 Version 01 (1) │ │ │ │ +A0715 UID Size 04 (4) │ │ │ │ +A0716 UID 00000000 (0) │ │ │ │ +A071A GID Size 04 (4) │ │ │ │ +A071B GID 00000000 (0) │ │ │ │ + │ │ │ │ +A071F CENTRAL HEADER #51 02014B50 (33639248) │ │ │ │ +A0723 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0724 Created OS 03 (3) 'Unix' │ │ │ │ +A0725 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0726 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0727 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0729 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A072B Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A072F CRC EEF96960 (4009322848) │ │ │ │ +A0733 Compressed Size 000014D7 (5335) │ │ │ │ +A0737 Uncompressed Size 00006893 (26771) │ │ │ │ +A073B Filename Length 0012 (18) │ │ │ │ +A073D Extra Length 0018 (24) │ │ │ │ +A073F Comment Length 0000 (0) │ │ │ │ +A0741 Disk Start 0000 (0) │ │ │ │ +A0743 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0745 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0749 Local Header Offset 00060FDC (397276) │ │ │ │ +A074D Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA074D: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A075F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0761 Length 0005 (5) │ │ │ │ +A0763 Flags 01 (1) 'Modification' │ │ │ │ +A0764 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0768 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A076A Length 000B (11) │ │ │ │ +A076C Version 01 (1) │ │ │ │ +A076D UID Size 04 (4) │ │ │ │ +A076E UID 00000000 (0) │ │ │ │ +A0772 GID Size 04 (4) │ │ │ │ +A0773 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0777 CENTRAL HEADER #52 02014B50 (33639248) │ │ │ │ +A077B Created Zip Spec 3D (61) '6.1' │ │ │ │ +A077C Created OS 03 (3) 'Unix' │ │ │ │ +A077D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A077E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A077F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0781 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0783 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0787 CRC 37877832 (931625010) │ │ │ │ +A078B Compressed Size 000011F1 (4593) │ │ │ │ +A078F Uncompressed Size 0000410D (16653) │ │ │ │ +A0793 Filename Length 0012 (18) │ │ │ │ +A0795 Extra Length 0018 (24) │ │ │ │ +A0797 Comment Length 0000 (0) │ │ │ │ +A0799 Disk Start 0000 (0) │ │ │ │ +A079B Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A079D Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A07A1 Local Header Offset 000624FF (402687) │ │ │ │ +A07A5 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA07A5: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A07B7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A07B9 Length 0005 (5) │ │ │ │ +A07BB Flags 01 (1) 'Modification' │ │ │ │ +A07BC Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A07C0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A07C2 Length 000B (11) │ │ │ │ +A07C4 Version 01 (1) │ │ │ │ +A07C5 UID Size 04 (4) │ │ │ │ +A07C6 UID 00000000 (0) │ │ │ │ +A07CA GID Size 04 (4) │ │ │ │ +A07CB GID 00000000 (0) │ │ │ │ + │ │ │ │ +A07CF CENTRAL HEADER #53 02014B50 (33639248) │ │ │ │ +A07D3 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A07D4 Created OS 03 (3) 'Unix' │ │ │ │ +A07D5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A07D6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A07D7 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A07D9 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A07DB Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A07DF CRC 1410FCEA (336657642) │ │ │ │ +A07E3 Compressed Size 000009DB (2523) │ │ │ │ +A07E7 Uncompressed Size 0000352A (13610) │ │ │ │ +A07EB Filename Length 0019 (25) │ │ │ │ +A07ED Extra Length 0018 (24) │ │ │ │ +A07EF Comment Length 0000 (0) │ │ │ │ +A07F1 Disk Start 0000 (0) │ │ │ │ +A07F3 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A07F5 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A07F9 Local Header Offset 0006373C (407356) │ │ │ │ +A07FD Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA07FD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0816 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0818 Length 0005 (5) │ │ │ │ +A081A Flags 01 (1) 'Modification' │ │ │ │ +A081B Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A081F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0821 Length 000B (11) │ │ │ │ +A0823 Version 01 (1) │ │ │ │ +A0824 UID Size 04 (4) │ │ │ │ +A0825 UID 00000000 (0) │ │ │ │ +A0829 GID Size 04 (4) │ │ │ │ +A082A GID 00000000 (0) │ │ │ │ + │ │ │ │ +A082E CENTRAL HEADER #54 02014B50 (33639248) │ │ │ │ +A0832 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0833 Created OS 03 (3) 'Unix' │ │ │ │ +A0834 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0835 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0836 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0838 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A083A Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A083E CRC 64462F48 (1682321224) │ │ │ │ +A0842 Compressed Size 00002021 (8225) │ │ │ │ +A0846 Uncompressed Size 00010945 (67909) │ │ │ │ +A084A Filename Length 0019 (25) │ │ │ │ +A084C Extra Length 0018 (24) │ │ │ │ +A084E Comment Length 0000 (0) │ │ │ │ +A0850 Disk Start 0000 (0) │ │ │ │ +A0852 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0854 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0858 Local Header Offset 0006416A (409962) │ │ │ │ +A085C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA085C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0875 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0877 Length 0005 (5) │ │ │ │ +A0879 Flags 01 (1) 'Modification' │ │ │ │ +A087A Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A087E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0880 Length 000B (11) │ │ │ │ +A0882 Version 01 (1) │ │ │ │ +A0883 UID Size 04 (4) │ │ │ │ +A0884 UID 00000000 (0) │ │ │ │ +A0888 GID Size 04 (4) │ │ │ │ +A0889 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A088D CENTRAL HEADER #55 02014B50 (33639248) │ │ │ │ +A0891 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0892 Created OS 03 (3) 'Unix' │ │ │ │ +A0893 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0894 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0895 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0897 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0899 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A089D CRC D0372DB1 (3493277105) │ │ │ │ +A08A1 Compressed Size 0000177F (6015) │ │ │ │ +A08A5 Uncompressed Size 0000472D (18221) │ │ │ │ +A08A9 Filename Length 0014 (20) │ │ │ │ +A08AB Extra Length 0018 (24) │ │ │ │ +A08AD Comment Length 0000 (0) │ │ │ │ +A08AF Disk Start 0000 (0) │ │ │ │ +A08B1 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A08B3 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A08B7 Local Header Offset 000661DE (418270) │ │ │ │ +A08BB Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA08BB: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A08CF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A08D1 Length 0005 (5) │ │ │ │ +A08D3 Flags 01 (1) 'Modification' │ │ │ │ +A08D4 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A08D8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A08DA Length 000B (11) │ │ │ │ +A08DC Version 01 (1) │ │ │ │ +A08DD UID Size 04 (4) │ │ │ │ +A08DE UID 00000000 (0) │ │ │ │ +A08E2 GID Size 04 (4) │ │ │ │ +A08E3 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A08E7 CENTRAL HEADER #56 02014B50 (33639248) │ │ │ │ +A08EB Created Zip Spec 3D (61) '6.1' │ │ │ │ +A08EC Created OS 03 (3) 'Unix' │ │ │ │ +A08ED Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A08EE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A08EF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A08F1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A08F3 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A08F7 CRC FAE30038 (4209180728) │ │ │ │ +A08FB Compressed Size 0000040B (1035) │ │ │ │ +A08FF Uncompressed Size 00000826 (2086) │ │ │ │ +A0903 Filename Length 001C (28) │ │ │ │ +A0905 Extra Length 0018 (24) │ │ │ │ +A0907 Comment Length 0000 (0) │ │ │ │ +A0909 Disk Start 0000 (0) │ │ │ │ +A090B Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A090D Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0911 Local Header Offset 000679AB (424363) │ │ │ │ +A0915 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0915: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0931 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0933 Length 0005 (5) │ │ │ │ +A0935 Flags 01 (1) 'Modification' │ │ │ │ +A0936 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A093A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A093C Length 000B (11) │ │ │ │ +A093E Version 01 (1) │ │ │ │ +A093F UID Size 04 (4) │ │ │ │ +A0940 UID 00000000 (0) │ │ │ │ +A0944 GID Size 04 (4) │ │ │ │ +A0945 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0949 CENTRAL HEADER #57 02014B50 (33639248) │ │ │ │ +A094D Created Zip Spec 3D (61) '6.1' │ │ │ │ +A094E Created OS 03 (3) 'Unix' │ │ │ │ +A094F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0950 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0951 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0953 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0955 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0959 CRC E9FBA175 (3925582197) │ │ │ │ +A095D Compressed Size 0000249B (9371) │ │ │ │ +A0961 Uncompressed Size 0000B5FA (46586) │ │ │ │ +A0965 Filename Length 001F (31) │ │ │ │ +A0967 Extra Length 0018 (24) │ │ │ │ +A0969 Comment Length 0000 (0) │ │ │ │ +A096B Disk Start 0000 (0) │ │ │ │ +A096D Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A096F Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0973 Local Header Offset 00067E0C (425484) │ │ │ │ +A0977 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0977: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0996 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0998 Length 0005 (5) │ │ │ │ +A099A Flags 01 (1) 'Modification' │ │ │ │ +A099B Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A099F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A09A1 Length 000B (11) │ │ │ │ +A09A3 Version 01 (1) │ │ │ │ +A09A4 UID Size 04 (4) │ │ │ │ +A09A5 UID 00000000 (0) │ │ │ │ +A09A9 GID Size 04 (4) │ │ │ │ +A09AA GID 00000000 (0) │ │ │ │ + │ │ │ │ +A09AE CENTRAL HEADER #58 02014B50 (33639248) │ │ │ │ +A09B2 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A09B3 Created OS 03 (3) 'Unix' │ │ │ │ +A09B4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A09B5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A09B6 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A09B8 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A09BA Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A09BE CRC 1AD87814 (450394132) │ │ │ │ +A09C2 Compressed Size 00000E80 (3712) │ │ │ │ +A09C6 Uncompressed Size 000052DA (21210) │ │ │ │ +A09CA Filename Length 001F (31) │ │ │ │ +A09CC Extra Length 0018 (24) │ │ │ │ +A09CE Comment Length 0000 (0) │ │ │ │ +A09D0 Disk Start 0000 (0) │ │ │ │ +A09D2 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A09D4 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A09D8 Local Header Offset 0006A300 (434944) │ │ │ │ +A09DC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA09DC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A09FB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A09FD Length 0005 (5) │ │ │ │ +A09FF Flags 01 (1) 'Modification' │ │ │ │ +A0A00 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0A04 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0A06 Length 000B (11) │ │ │ │ +A0A08 Version 01 (1) │ │ │ │ +A0A09 UID Size 04 (4) │ │ │ │ +A0A0A UID 00000000 (0) │ │ │ │ +A0A0E GID Size 04 (4) │ │ │ │ +A0A0F GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0A13 CENTRAL HEADER #59 02014B50 (33639248) │ │ │ │ +A0A17 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0A18 Created OS 03 (3) 'Unix' │ │ │ │ +A0A19 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0A1A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0A1B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0A1D Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0A1F Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0A23 CRC 4DE022CE (1306534606) │ │ │ │ +A0A27 Compressed Size 00000A45 (2629) │ │ │ │ +A0A2B Uncompressed Size 0000247B (9339) │ │ │ │ +A0A2F Filename Length 0013 (19) │ │ │ │ +A0A31 Extra Length 0018 (24) │ │ │ │ +A0A33 Comment Length 0000 (0) │ │ │ │ +A0A35 Disk Start 0000 (0) │ │ │ │ +A0A37 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0A39 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0A3D Local Header Offset 0006B1D9 (438745) │ │ │ │ +A0A41 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0A41: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0A54 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0A56 Length 0005 (5) │ │ │ │ +A0A58 Flags 01 (1) 'Modification' │ │ │ │ +A0A59 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0A5D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0A5F Length 000B (11) │ │ │ │ +A0A61 Version 01 (1) │ │ │ │ +A0A62 UID Size 04 (4) │ │ │ │ +A0A63 UID 00000000 (0) │ │ │ │ +A0A67 GID Size 04 (4) │ │ │ │ +A0A68 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0A6C CENTRAL HEADER #60 02014B50 (33639248) │ │ │ │ +A0A70 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0A71 Created OS 03 (3) 'Unix' │ │ │ │ +A0A72 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0A73 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0A74 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0A76 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0A78 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0A7C CRC 226C1C3B (577510459) │ │ │ │ +A0A80 Compressed Size 0000248D (9357) │ │ │ │ +A0A84 Uncompressed Size 0000B84D (47181) │ │ │ │ +A0A88 Filename Length 0019 (25) │ │ │ │ +A0A8A Extra Length 0018 (24) │ │ │ │ +A0A8C Comment Length 0000 (0) │ │ │ │ +A0A8E Disk Start 0000 (0) │ │ │ │ +A0A90 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0A92 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0A96 Local Header Offset 0006BC6B (441451) │ │ │ │ +A0A9A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0A9A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0AB3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0AB5 Length 0005 (5) │ │ │ │ +A0AB7 Flags 01 (1) 'Modification' │ │ │ │ +A0AB8 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0ABC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0ABE Length 000B (11) │ │ │ │ +A0AC0 Version 01 (1) │ │ │ │ +A0AC1 UID Size 04 (4) │ │ │ │ +A0AC2 UID 00000000 (0) │ │ │ │ +A0AC6 GID Size 04 (4) │ │ │ │ +A0AC7 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0ACB CENTRAL HEADER #61 02014B50 (33639248) │ │ │ │ +A0ACF Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0AD0 Created OS 03 (3) 'Unix' │ │ │ │ +A0AD1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0AD2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0AD3 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0AD5 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0AD7 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0ADB CRC 687822FC (1752703740) │ │ │ │ +A0ADF Compressed Size 00000EF8 (3832) │ │ │ │ +A0AE3 Uncompressed Size 00003A2D (14893) │ │ │ │ +A0AE7 Filename Length 0024 (36) │ │ │ │ +A0AE9 Extra Length 0018 (24) │ │ │ │ +A0AEB Comment Length 0000 (0) │ │ │ │ +A0AED Disk Start 0000 (0) │ │ │ │ +A0AEF Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0AF1 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0AF5 Local Header Offset 0006E14B (450891) │ │ │ │ +A0AF9 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0AF9: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0B1D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0B1F Length 0005 (5) │ │ │ │ +A0B21 Flags 01 (1) 'Modification' │ │ │ │ +A0B22 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0B26 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0B28 Length 000B (11) │ │ │ │ +A0B2A Version 01 (1) │ │ │ │ +A0B2B UID Size 04 (4) │ │ │ │ +A0B2C UID 00000000 (0) │ │ │ │ +A0B30 GID Size 04 (4) │ │ │ │ +A0B31 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0B35 CENTRAL HEADER #62 02014B50 (33639248) │ │ │ │ +A0B39 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0B3A Created OS 03 (3) 'Unix' │ │ │ │ +A0B3B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0B3C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0B3D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0B3F Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0B41 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0B45 CRC 726CE69D (1919739549) │ │ │ │ +A0B49 Compressed Size 00001AB9 (6841) │ │ │ │ +A0B4D Uncompressed Size 00005F39 (24377) │ │ │ │ +A0B51 Filename Length 0017 (23) │ │ │ │ +A0B53 Extra Length 0018 (24) │ │ │ │ +A0B55 Comment Length 0000 (0) │ │ │ │ +A0B57 Disk Start 0000 (0) │ │ │ │ +A0B59 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0B5B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0B5F Local Header Offset 0006F0A1 (454817) │ │ │ │ +A0B63 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0B63: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0B7A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0B7C Length 0005 (5) │ │ │ │ +A0B7E Flags 01 (1) 'Modification' │ │ │ │ +A0B7F Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0B83 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0B85 Length 000B (11) │ │ │ │ +A0B87 Version 01 (1) │ │ │ │ +A0B88 UID Size 04 (4) │ │ │ │ +A0B89 UID 00000000 (0) │ │ │ │ +A0B8D GID Size 04 (4) │ │ │ │ +A0B8E GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0B92 CENTRAL HEADER #63 02014B50 (33639248) │ │ │ │ +A0B96 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0B97 Created OS 03 (3) 'Unix' │ │ │ │ +A0B98 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0B99 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0B9A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0B9C Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0B9E Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0BA2 CRC 3DD8544C (1037587532) │ │ │ │ +A0BA6 Compressed Size 00000ED1 (3793) │ │ │ │ +A0BAA Uncompressed Size 000038DE (14558) │ │ │ │ +A0BAE Filename Length 0023 (35) │ │ │ │ +A0BB0 Extra Length 0018 (24) │ │ │ │ +A0BB2 Comment Length 0000 (0) │ │ │ │ +A0BB4 Disk Start 0000 (0) │ │ │ │ +A0BB6 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0BB8 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0BBC Local Header Offset 00070BAB (461739) │ │ │ │ +A0BC0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0BC0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0BE3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0BE5 Length 0005 (5) │ │ │ │ +A0BE7 Flags 01 (1) 'Modification' │ │ │ │ +A0BE8 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0BEC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0BEE Length 000B (11) │ │ │ │ +A0BF0 Version 01 (1) │ │ │ │ +A0BF1 UID Size 04 (4) │ │ │ │ +A0BF2 UID 00000000 (0) │ │ │ │ +A0BF6 GID Size 04 (4) │ │ │ │ +A0BF7 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0BFB CENTRAL HEADER #64 02014B50 (33639248) │ │ │ │ +A0BFF Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0C00 Created OS 03 (3) 'Unix' │ │ │ │ +A0C01 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0C02 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0C03 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0C05 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0C07 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0C0B CRC 2DB7929F (767005343) │ │ │ │ +A0C0F Compressed Size 00000113 (275) │ │ │ │ +A0C13 Uncompressed Size 000001F3 (499) │ │ │ │ +A0C17 Filename Length 001B (27) │ │ │ │ +A0C19 Extra Length 0018 (24) │ │ │ │ +A0C1B Comment Length 0000 (0) │ │ │ │ +A0C1D Disk Start 0000 (0) │ │ │ │ +A0C1F Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0C21 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0C25 Local Header Offset 00071AD9 (465625) │ │ │ │ +A0C29 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0C29: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0C44 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0C46 Length 0005 (5) │ │ │ │ +A0C48 Flags 01 (1) 'Modification' │ │ │ │ +A0C49 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0C4D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0C4F Length 000B (11) │ │ │ │ +A0C51 Version 01 (1) │ │ │ │ +A0C52 UID Size 04 (4) │ │ │ │ +A0C53 UID 00000000 (0) │ │ │ │ +A0C57 GID Size 04 (4) │ │ │ │ +A0C58 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0C5C CENTRAL HEADER #65 02014B50 (33639248) │ │ │ │ +A0C60 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0C61 Created OS 03 (3) 'Unix' │ │ │ │ +A0C62 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0C63 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0C64 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0C66 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0C68 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0C6C CRC 8C77D705 (2356664069) │ │ │ │ +A0C70 Compressed Size 00001892 (6290) │ │ │ │ +A0C74 Uncompressed Size 00008FAD (36781) │ │ │ │ +A0C78 Filename Length 001D (29) │ │ │ │ +A0C7A Extra Length 0018 (24) │ │ │ │ +A0C7C Comment Length 0000 (0) │ │ │ │ +A0C7E Disk Start 0000 (0) │ │ │ │ +A0C80 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0C82 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0C86 Local Header Offset 00071C41 (465985) │ │ │ │ +A0C8A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0C8A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0CA7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0CA9 Length 0005 (5) │ │ │ │ +A0CAB Flags 01 (1) 'Modification' │ │ │ │ +A0CAC Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0CB0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0CB2 Length 000B (11) │ │ │ │ +A0CB4 Version 01 (1) │ │ │ │ +A0CB5 UID Size 04 (4) │ │ │ │ +A0CB6 UID 00000000 (0) │ │ │ │ +A0CBA GID Size 04 (4) │ │ │ │ +A0CBB GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0CBF CENTRAL HEADER #66 02014B50 (33639248) │ │ │ │ +A0CC3 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0CC4 Created OS 03 (3) 'Unix' │ │ │ │ +A0CC5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0CC6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0CC7 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0CC9 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0CCB Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0CCF CRC 516C0F5D (1366036317) │ │ │ │ +A0CD3 Compressed Size 0000164A (5706) │ │ │ │ +A0CD7 Uncompressed Size 00003A9C (15004) │ │ │ │ +A0CDB Filename Length 0015 (21) │ │ │ │ +A0CDD Extra Length 0018 (24) │ │ │ │ +A0CDF Comment Length 0000 (0) │ │ │ │ +A0CE1 Disk Start 0000 (0) │ │ │ │ +A0CE3 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0CE5 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0CE9 Local Header Offset 0007352A (472362) │ │ │ │ +A0CED Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0CED: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0D02 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0D04 Length 0005 (5) │ │ │ │ +A0D06 Flags 01 (1) 'Modification' │ │ │ │ +A0D07 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0D0B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0D0D Length 000B (11) │ │ │ │ +A0D0F Version 01 (1) │ │ │ │ +A0D10 UID Size 04 (4) │ │ │ │ +A0D11 UID 00000000 (0) │ │ │ │ +A0D15 GID Size 04 (4) │ │ │ │ +A0D16 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0D1A CENTRAL HEADER #67 02014B50 (33639248) │ │ │ │ +A0D1E Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0D1F Created OS 03 (3) 'Unix' │ │ │ │ +A0D20 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0D21 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0D22 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0D24 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0D26 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0D2A CRC A1AA00A3 (2712273059) │ │ │ │ +A0D2E Compressed Size 00003B52 (15186) │ │ │ │ +A0D32 Uncompressed Size 00011CC3 (72899) │ │ │ │ +A0D36 Filename Length 0016 (22) │ │ │ │ +A0D38 Extra Length 0018 (24) │ │ │ │ +A0D3A Comment Length 0000 (0) │ │ │ │ +A0D3C Disk Start 0000 (0) │ │ │ │ +A0D3E Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0D40 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0D44 Local Header Offset 00074BC3 (478147) │ │ │ │ +A0D48 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0D48: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0D5E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0D60 Length 0005 (5) │ │ │ │ +A0D62 Flags 01 (1) 'Modification' │ │ │ │ +A0D63 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0D67 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0D69 Length 000B (11) │ │ │ │ +A0D6B Version 01 (1) │ │ │ │ +A0D6C UID Size 04 (4) │ │ │ │ +A0D6D UID 00000000 (0) │ │ │ │ +A0D71 GID Size 04 (4) │ │ │ │ +A0D72 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0D76 CENTRAL HEADER #68 02014B50 (33639248) │ │ │ │ +A0D7A Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0D7B Created OS 03 (3) 'Unix' │ │ │ │ +A0D7C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0D7D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0D7E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0D80 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0D82 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0D86 CRC 26977330 (647459632) │ │ │ │ +A0D8A Compressed Size 00003EC8 (16072) │ │ │ │ +A0D8E Uncompressed Size 0001C79C (116636) │ │ │ │ +A0D92 Filename Length 0019 (25) │ │ │ │ +A0D94 Extra Length 0018 (24) │ │ │ │ +A0D96 Comment Length 0000 (0) │ │ │ │ +A0D98 Disk Start 0000 (0) │ │ │ │ +A0D9A Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0D9C Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0DA0 Local Header Offset 00078765 (493413) │ │ │ │ +A0DA4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0DA4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0DBD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0DBF Length 0005 (5) │ │ │ │ +A0DC1 Flags 01 (1) 'Modification' │ │ │ │ +A0DC2 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0DC6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0DC8 Length 000B (11) │ │ │ │ +A0DCA Version 01 (1) │ │ │ │ +A0DCB UID Size 04 (4) │ │ │ │ +A0DCC UID 00000000 (0) │ │ │ │ +A0DD0 GID Size 04 (4) │ │ │ │ +A0DD1 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0DD5 CENTRAL HEADER #69 02014B50 (33639248) │ │ │ │ +A0DD9 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0DDA Created OS 03 (3) 'Unix' │ │ │ │ +A0DDB Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0DDC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0DDD General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0DDF Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0DE1 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0DE5 CRC 510D6570 (1359832432) │ │ │ │ +A0DE9 Compressed Size 00000838 (2104) │ │ │ │ +A0DED Uncompressed Size 00003384 (13188) │ │ │ │ +A0DF1 Filename Length 0011 (17) │ │ │ │ +A0DF3 Extra Length 0018 (24) │ │ │ │ +A0DF5 Comment Length 0000 (0) │ │ │ │ +A0DF7 Disk Start 0000 (0) │ │ │ │ +A0DF9 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0DFB Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0DFF Local Header Offset 0007C680 (509568) │ │ │ │ +A0E03 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0E03: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0E14 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0E16 Length 0005 (5) │ │ │ │ +A0E18 Flags 01 (1) 'Modification' │ │ │ │ +A0E19 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0E1D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0E1F Length 000B (11) │ │ │ │ +A0E21 Version 01 (1) │ │ │ │ +A0E22 UID Size 04 (4) │ │ │ │ +A0E23 UID 00000000 (0) │ │ │ │ +A0E27 GID Size 04 (4) │ │ │ │ +A0E28 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0E2C CENTRAL HEADER #70 02014B50 (33639248) │ │ │ │ +A0E30 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0E31 Created OS 03 (3) 'Unix' │ │ │ │ +A0E32 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0E33 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0E34 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0E36 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0E38 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0E3C CRC B744432E (3074704174) │ │ │ │ +A0E40 Compressed Size 000051A7 (20903) │ │ │ │ +A0E44 Uncompressed Size 0001FBE0 (130016) │ │ │ │ +A0E48 Filename Length 0015 (21) │ │ │ │ +A0E4A Extra Length 0018 (24) │ │ │ │ +A0E4C Comment Length 0000 (0) │ │ │ │ +A0E4E Disk Start 0000 (0) │ │ │ │ +A0E50 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0E52 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0E56 Local Header Offset 0007CF03 (511747) │ │ │ │ +A0E5A Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0E5A: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0E6F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0E71 Length 0005 (5) │ │ │ │ +A0E73 Flags 01 (1) 'Modification' │ │ │ │ +A0E74 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0E78 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0E7A Length 000B (11) │ │ │ │ +A0E7C Version 01 (1) │ │ │ │ +A0E7D UID Size 04 (4) │ │ │ │ +A0E7E UID 00000000 (0) │ │ │ │ +A0E82 GID Size 04 (4) │ │ │ │ +A0E83 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0E87 CENTRAL HEADER #71 02014B50 (33639248) │ │ │ │ +A0E8B Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0E8C Created OS 03 (3) 'Unix' │ │ │ │ +A0E8D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0E8E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0E8F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0E91 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0E93 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0E97 CRC 5D0D793D (1561164093) │ │ │ │ +A0E9B Compressed Size 00001B0A (6922) │ │ │ │ +A0E9F Uncompressed Size 000081D0 (33232) │ │ │ │ +A0EA3 Filename Length 0019 (25) │ │ │ │ +A0EA5 Extra Length 0018 (24) │ │ │ │ +A0EA7 Comment Length 0000 (0) │ │ │ │ +A0EA9 Disk Start 0000 (0) │ │ │ │ +A0EAB Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0EAD Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0EB1 Local Header Offset 000820F9 (532729) │ │ │ │ +A0EB5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0EB5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0ECE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0ED0 Length 0005 (5) │ │ │ │ +A0ED2 Flags 01 (1) 'Modification' │ │ │ │ +A0ED3 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0ED7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0ED9 Length 000B (11) │ │ │ │ +A0EDB Version 01 (1) │ │ │ │ +A0EDC UID Size 04 (4) │ │ │ │ +A0EDD UID 00000000 (0) │ │ │ │ +A0EE1 GID Size 04 (4) │ │ │ │ +A0EE2 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0EE6 CENTRAL HEADER #72 02014B50 (33639248) │ │ │ │ +A0EEA Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0EEB Created OS 03 (3) 'Unix' │ │ │ │ +A0EEC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0EED Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0EEE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0EF0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0EF2 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0EF6 CRC 392C251C (959194396) │ │ │ │ +A0EFA Compressed Size 00000D9A (3482) │ │ │ │ +A0EFE Uncompressed Size 00002EA0 (11936) │ │ │ │ +A0F02 Filename Length 0018 (24) │ │ │ │ +A0F04 Extra Length 0018 (24) │ │ │ │ +A0F06 Comment Length 0000 (0) │ │ │ │ +A0F08 Disk Start 0000 (0) │ │ │ │ +A0F0A Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0F0C Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0F10 Local Header Offset 00083C56 (539734) │ │ │ │ +A0F14 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0F14: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0F2C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0F2E Length 0005 (5) │ │ │ │ +A0F30 Flags 01 (1) 'Modification' │ │ │ │ +A0F31 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0F35 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0F37 Length 000B (11) │ │ │ │ +A0F39 Version 01 (1) │ │ │ │ +A0F3A UID Size 04 (4) │ │ │ │ +A0F3B UID 00000000 (0) │ │ │ │ +A0F3F GID Size 04 (4) │ │ │ │ +A0F40 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0F44 CENTRAL HEADER #73 02014B50 (33639248) │ │ │ │ +A0F48 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0F49 Created OS 03 (3) 'Unix' │ │ │ │ +A0F4A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0F4B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0F4C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0F4E Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0F50 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0F54 CRC 037E3A9E (58604190) │ │ │ │ +A0F58 Compressed Size 000001E1 (481) │ │ │ │ +A0F5C Uncompressed Size 00000324 (804) │ │ │ │ +A0F60 Filename Length 0011 (17) │ │ │ │ +A0F62 Extra Length 0018 (24) │ │ │ │ +A0F64 Comment Length 0000 (0) │ │ │ │ +A0F66 Disk Start 0000 (0) │ │ │ │ +A0F68 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0F6A Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0F6E Local Header Offset 00084A42 (543298) │ │ │ │ +A0F72 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0F72: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0F83 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0F85 Length 0005 (5) │ │ │ │ +A0F87 Flags 01 (1) 'Modification' │ │ │ │ +A0F88 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0F8C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0F8E Length 000B (11) │ │ │ │ +A0F90 Version 01 (1) │ │ │ │ +A0F91 UID Size 04 (4) │ │ │ │ +A0F92 UID 00000000 (0) │ │ │ │ +A0F96 GID Size 04 (4) │ │ │ │ +A0F97 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0F9B CENTRAL HEADER #74 02014B50 (33639248) │ │ │ │ +A0F9F Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0FA0 Created OS 03 (3) 'Unix' │ │ │ │ +A0FA1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0FA2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0FA3 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0FA5 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0FA7 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A0FAB CRC 55187787 (1427666823) │ │ │ │ +A0FAF Compressed Size 000006C3 (1731) │ │ │ │ +A0FB3 Uncompressed Size 0000143A (5178) │ │ │ │ +A0FB7 Filename Length 0019 (25) │ │ │ │ +A0FB9 Extra Length 0018 (24) │ │ │ │ +A0FBB Comment Length 0000 (0) │ │ │ │ +A0FBD Disk Start 0000 (0) │ │ │ │ +A0FBF Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0FC1 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0FC5 Local Header Offset 00084C6E (543854) │ │ │ │ +A0FC9 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0FC9: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0FE2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0FE4 Length 0005 (5) │ │ │ │ +A0FE6 Flags 01 (1) 'Modification' │ │ │ │ +A0FE7 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A0FEB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0FED Length 000B (11) │ │ │ │ +A0FEF Version 01 (1) │ │ │ │ +A0FF0 UID Size 04 (4) │ │ │ │ +A0FF1 UID 00000000 (0) │ │ │ │ +A0FF5 GID Size 04 (4) │ │ │ │ +A0FF6 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0FFA CENTRAL HEADER #75 02014B50 (33639248) │ │ │ │ +A0FFE Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0FFF Created OS 03 (3) 'Unix' │ │ │ │ +A1000 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A1001 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A1002 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A1004 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A1006 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A100A CRC 107EE648 (276751944) │ │ │ │ +A100E Compressed Size 00001EAC (7852) │ │ │ │ +A1012 Uncompressed Size 0000CA81 (51841) │ │ │ │ +A1016 Filename Length 0018 (24) │ │ │ │ +A1018 Extra Length 0018 (24) │ │ │ │ +A101A Comment Length 0000 (0) │ │ │ │ +A101C Disk Start 0000 (0) │ │ │ │ +A101E Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A1020 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A1024 Local Header Offset 00085384 (545668) │ │ │ │ +A1028 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA1028: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A1040 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A1042 Length 0005 (5) │ │ │ │ +A1044 Flags 01 (1) 'Modification' │ │ │ │ +A1045 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A1049 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A104B Length 000B (11) │ │ │ │ +A104D Version 01 (1) │ │ │ │ +A104E UID Size 04 (4) │ │ │ │ +A104F UID 00000000 (0) │ │ │ │ +A1053 GID Size 04 (4) │ │ │ │ +A1054 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A1058 CENTRAL HEADER #76 02014B50 (33639248) │ │ │ │ +A105C Created Zip Spec 3D (61) '6.1' │ │ │ │ +A105D Created OS 03 (3) 'Unix' │ │ │ │ +A105E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A105F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A1060 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A1062 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A1064 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A1068 CRC 45F6BF5A (1173798746) │ │ │ │ +A106C Compressed Size 00001BEB (7147) │ │ │ │ +A1070 Uncompressed Size 0000C5EA (50666) │ │ │ │ +A1074 Filename Length 0012 (18) │ │ │ │ +A1076 Extra Length 0018 (24) │ │ │ │ +A1078 Comment Length 0000 (0) │ │ │ │ +A107A Disk Start 0000 (0) │ │ │ │ +A107C Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A107E Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A1082 Local Header Offset 00087282 (553602) │ │ │ │ +A1086 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA1086: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A1098 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A109A Length 0005 (5) │ │ │ │ +A109C Flags 01 (1) 'Modification' │ │ │ │ +A109D Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A10A1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A10A3 Length 000B (11) │ │ │ │ +A10A5 Version 01 (1) │ │ │ │ +A10A6 UID Size 04 (4) │ │ │ │ +A10A7 UID 00000000 (0) │ │ │ │ +A10AB GID Size 04 (4) │ │ │ │ +A10AC GID 00000000 (0) │ │ │ │ + │ │ │ │ +A10B0 CENTRAL HEADER #77 02014B50 (33639248) │ │ │ │ +A10B4 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A10B5 Created OS 03 (3) 'Unix' │ │ │ │ +A10B6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A10B7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A10B8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A10BA Compression Method 0008 (8) 'Deflated' │ │ │ │ +A10BC Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A10C0 CRC 317BA035 (830185525) │ │ │ │ +A10C4 Compressed Size 00001E13 (7699) │ │ │ │ +A10C8 Uncompressed Size 00008804 (34820) │ │ │ │ +A10CC Filename Length 0016 (22) │ │ │ │ +A10CE Extra Length 0018 (24) │ │ │ │ +A10D0 Comment Length 0000 (0) │ │ │ │ +A10D2 Disk Start 0000 (0) │ │ │ │ +A10D4 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A10D6 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A10DA Local Header Offset 00088EB9 (560825) │ │ │ │ +A10DE Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA10DE: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A10F4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A10F6 Length 0005 (5) │ │ │ │ +A10F8 Flags 01 (1) 'Modification' │ │ │ │ +A10F9 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A10FD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A10FF Length 000B (11) │ │ │ │ +A1101 Version 01 (1) │ │ │ │ +A1102 UID Size 04 (4) │ │ │ │ +A1103 UID 00000000 (0) │ │ │ │ +A1107 GID Size 04 (4) │ │ │ │ +A1108 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A110C CENTRAL HEADER #78 02014B50 (33639248) │ │ │ │ +A1110 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A1111 Created OS 03 (3) 'Unix' │ │ │ │ +A1112 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A1113 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A1114 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A1116 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A1118 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A111C CRC B0584996 (2958576022) │ │ │ │ +A1120 Compressed Size 000029AA (10666) │ │ │ │ +A1124 Uncompressed Size 0000D050 (53328) │ │ │ │ +A1128 Filename Length 001A (26) │ │ │ │ +A112A Extra Length 0018 (24) │ │ │ │ +A112C Comment Length 0000 (0) │ │ │ │ +A112E Disk Start 0000 (0) │ │ │ │ +A1130 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A1132 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A1136 Local Header Offset 0008AD1C (568604) │ │ │ │ +A113A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA113A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A1154 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A1156 Length 0005 (5) │ │ │ │ +A1158 Flags 01 (1) 'Modification' │ │ │ │ +A1159 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A115D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A115F Length 000B (11) │ │ │ │ +A1161 Version 01 (1) │ │ │ │ +A1162 UID Size 04 (4) │ │ │ │ +A1163 UID 00000000 (0) │ │ │ │ +A1167 GID Size 04 (4) │ │ │ │ +A1168 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A116C CENTRAL HEADER #79 02014B50 (33639248) │ │ │ │ +A1170 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A1171 Created OS 03 (3) 'Unix' │ │ │ │ +A1172 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A1173 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A1174 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A1176 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A1178 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A117C CRC 7FA7262B (2141660715) │ │ │ │ +A1180 Compressed Size 000009AD (2477) │ │ │ │ +A1184 Uncompressed Size 00001DB7 (7607) │ │ │ │ +A1188 Filename Length 0018 (24) │ │ │ │ +A118A Extra Length 0018 (24) │ │ │ │ +A118C Comment Length 0000 (0) │ │ │ │ +A118E Disk Start 0000 (0) │ │ │ │ +A1190 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A1192 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A1196 Local Header Offset 0008D71A (579354) │ │ │ │ +A119A Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA119A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A11B2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A11B4 Length 0005 (5) │ │ │ │ +A11B6 Flags 01 (1) 'Modification' │ │ │ │ +A11B7 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A11BB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A11BD Length 000B (11) │ │ │ │ +A11BF Version 01 (1) │ │ │ │ +A11C0 UID Size 04 (4) │ │ │ │ +A11C1 UID 00000000 (0) │ │ │ │ +A11C5 GID Size 04 (4) │ │ │ │ +A11C6 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A11CA CENTRAL HEADER #80 02014B50 (33639248) │ │ │ │ +A11CE Created Zip Spec 3D (61) '6.1' │ │ │ │ +A11CF Created OS 03 (3) 'Unix' │ │ │ │ +A11D0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A11D1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A11D2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A11D4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A11D6 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A11DA CRC F5E2129F (4125233823) │ │ │ │ +A11DE Compressed Size 000016BC (5820) │ │ │ │ +A11E2 Uncompressed Size 000016CD (5837) │ │ │ │ +A11E6 Filename Length 0015 (21) │ │ │ │ +A11E8 Extra Length 0018 (24) │ │ │ │ +A11EA Comment Length 0000 (0) │ │ │ │ +A11EC Disk Start 0000 (0) │ │ │ │ +A11EE Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A11F0 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A11F4 Local Header Offset 0008E119 (581913) │ │ │ │ +A11F8 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA11F8: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A120D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A120F Length 0005 (5) │ │ │ │ +A1211 Flags 01 (1) 'Modification' │ │ │ │ +A1212 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A1216 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A1218 Length 000B (11) │ │ │ │ +A121A Version 01 (1) │ │ │ │ +A121B UID Size 04 (4) │ │ │ │ +A121C UID 00000000 (0) │ │ │ │ +A1220 GID Size 04 (4) │ │ │ │ +A1221 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A1225 CENTRAL HEADER #81 02014B50 (33639248) │ │ │ │ +A1229 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A122A Created OS 03 (3) 'Unix' │ │ │ │ +A122B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A122C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A122D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A122F Compression Method 0008 (8) 'Deflated' │ │ │ │ +A1231 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A1235 CRC F5E2129F (4125233823) │ │ │ │ +A1239 Compressed Size 000016BC (5820) │ │ │ │ +A123D Uncompressed Size 000016CD (5837) │ │ │ │ +A1241 Filename Length 001C (28) │ │ │ │ +A1243 Extra Length 0018 (24) │ │ │ │ +A1245 Comment Length 0000 (0) │ │ │ │ +A1247 Disk Start 0000 (0) │ │ │ │ +A1249 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A124B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A124F Local Header Offset 0008F824 (587812) │ │ │ │ +A1253 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA1253: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A126F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A1271 Length 0005 (5) │ │ │ │ +A1273 Flags 01 (1) 'Modification' │ │ │ │ +A1274 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A1278 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A127A Length 000B (11) │ │ │ │ +A127C Version 01 (1) │ │ │ │ +A127D UID Size 04 (4) │ │ │ │ +A127E UID 00000000 (0) │ │ │ │ +A1282 GID Size 04 (4) │ │ │ │ +A1283 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A1287 CENTRAL HEADER #82 02014B50 (33639248) │ │ │ │ +A128B Created Zip Spec 3D (61) '6.1' │ │ │ │ +A128C Created OS 03 (3) 'Unix' │ │ │ │ +A128D Extract Zip Spec 0A (10) '1.0' │ │ │ │ +A128E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A128F General Purpose Flag 0000 (0) │ │ │ │ +A1291 Compression Method 0000 (0) 'Stored' │ │ │ │ +A1293 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A1297 CRC FC95F24B (4237685323) │ │ │ │ +A129B Compressed Size 00001B84 (7044) │ │ │ │ +A129F Uncompressed Size 00001B84 (7044) │ │ │ │ +A12A3 Filename Length 0016 (22) │ │ │ │ +A12A5 Extra Length 0018 (24) │ │ │ │ +A12A7 Comment Length 0000 (0) │ │ │ │ +A12A9 Disk Start 0000 (0) │ │ │ │ +A12AB Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A12AD Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A12B1 Local Header Offset 00090F36 (593718) │ │ │ │ +A12B5 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA12B5: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A12CB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A12CD Length 0005 (5) │ │ │ │ +A12CF Flags 01 (1) 'Modification' │ │ │ │ +A12D0 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A12D4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A12D6 Length 000B (11) │ │ │ │ +A12D8 Version 01 (1) │ │ │ │ +A12D9 UID Size 04 (4) │ │ │ │ +A12DA UID 00000000 (0) │ │ │ │ +A12DE GID Size 04 (4) │ │ │ │ +A12DF GID 00000000 (0) │ │ │ │ + │ │ │ │ +A12E3 CENTRAL HEADER #83 02014B50 (33639248) │ │ │ │ +A12E7 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A12E8 Created OS 03 (3) 'Unix' │ │ │ │ +A12E9 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +A12EA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A12EB General Purpose Flag 0000 (0) │ │ │ │ +A12ED Compression Method 0000 (0) 'Stored' │ │ │ │ +A12EF Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A12F3 CRC D0D71F86 (3503759238) │ │ │ │ +A12F7 Compressed Size 00000B7B (2939) │ │ │ │ +A12FB Uncompressed Size 00000B7B (2939) │ │ │ │ +A12FF Filename Length 0016 (22) │ │ │ │ +A1301 Extra Length 0018 (24) │ │ │ │ +A1303 Comment Length 0000 (0) │ │ │ │ +A1305 Disk Start 0000 (0) │ │ │ │ +A1307 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A1309 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A130D Local Header Offset 00092B0A (600842) │ │ │ │ +A1311 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA1311: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A1327 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A1329 Length 0005 (5) │ │ │ │ +A132B Flags 01 (1) 'Modification' │ │ │ │ +A132C Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A1330 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A1332 Length 000B (11) │ │ │ │ +A1334 Version 01 (1) │ │ │ │ +A1335 UID Size 04 (4) │ │ │ │ +A1336 UID 00000000 (0) │ │ │ │ +A133A GID Size 04 (4) │ │ │ │ +A133B GID 00000000 (0) │ │ │ │ + │ │ │ │ +A133F CENTRAL HEADER #84 02014B50 (33639248) │ │ │ │ +A1343 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A1344 Created OS 03 (3) 'Unix' │ │ │ │ +A1345 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +A1346 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A1347 General Purpose Flag 0000 (0) │ │ │ │ +A1349 Compression Method 0000 (0) 'Stored' │ │ │ │ +A134B Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A134F CRC FFF9C4D2 (4294558930) │ │ │ │ +A1353 Compressed Size 0000138F (5007) │ │ │ │ +A1357 Uncompressed Size 0000138F (5007) │ │ │ │ +A135B Filename Length 0016 (22) │ │ │ │ +A135D Extra Length 0018 (24) │ │ │ │ +A135F Comment Length 0000 (0) │ │ │ │ +A1361 Disk Start 0000 (0) │ │ │ │ +A1363 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A1365 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A1369 Local Header Offset 000936D5 (603861) │ │ │ │ +A136D Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA136D: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A1383 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A1385 Length 0005 (5) │ │ │ │ +A1387 Flags 01 (1) 'Modification' │ │ │ │ +A1388 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A138C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A138E Length 000B (11) │ │ │ │ +A1390 Version 01 (1) │ │ │ │ +A1391 UID Size 04 (4) │ │ │ │ +A1392 UID 00000000 (0) │ │ │ │ +A1396 GID Size 04 (4) │ │ │ │ +A1397 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A139B CENTRAL HEADER #85 02014B50 (33639248) │ │ │ │ +A139F Created Zip Spec 3D (61) '6.1' │ │ │ │ +A13A0 Created OS 03 (3) 'Unix' │ │ │ │ +A13A1 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +A13A2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A13A3 General Purpose Flag 0000 (0) │ │ │ │ +A13A5 Compression Method 0000 (0) 'Stored' │ │ │ │ +A13A7 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A13AB CRC A1037E8E (2701360782) │ │ │ │ +A13AF Compressed Size 0000145E (5214) │ │ │ │ +A13B3 Uncompressed Size 0000145E (5214) │ │ │ │ +A13B7 Filename Length 0016 (22) │ │ │ │ +A13B9 Extra Length 0018 (24) │ │ │ │ +A13BB Comment Length 0000 (0) │ │ │ │ +A13BD Disk Start 0000 (0) │ │ │ │ +A13BF Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A13C1 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A13C5 Local Header Offset 00094AB4 (608948) │ │ │ │ +A13C9 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA13C9: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A13DF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A13E1 Length 0005 (5) │ │ │ │ +A13E3 Flags 01 (1) 'Modification' │ │ │ │ +A13E4 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A13E8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A13EA Length 000B (11) │ │ │ │ +A13EC Version 01 (1) │ │ │ │ +A13ED UID Size 04 (4) │ │ │ │ +A13EE UID 00000000 (0) │ │ │ │ +A13F2 GID Size 04 (4) │ │ │ │ +A13F3 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A13F7 CENTRAL HEADER #86 02014B50 (33639248) │ │ │ │ +A13FB Created Zip Spec 3D (61) '6.1' │ │ │ │ +A13FC Created OS 03 (3) 'Unix' │ │ │ │ +A13FD Extract Zip Spec 0A (10) '1.0' │ │ │ │ +A13FE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A13FF General Purpose Flag 0000 (0) │ │ │ │ +A1401 Compression Method 0000 (0) 'Stored' │ │ │ │ +A1403 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A1407 CRC 5E9E64F1 (1587438833) │ │ │ │ +A140B Compressed Size 000008EC (2284) │ │ │ │ +A140F Uncompressed Size 000008EC (2284) │ │ │ │ +A1413 Filename Length 0016 (22) │ │ │ │ +A1415 Extra Length 0018 (24) │ │ │ │ +A1417 Comment Length 0000 (0) │ │ │ │ +A1419 Disk Start 0000 (0) │ │ │ │ +A141B Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A141D Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A1421 Local Header Offset 00095F62 (614242) │ │ │ │ +A1425 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA1425: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A143B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A143D Length 0005 (5) │ │ │ │ +A143F Flags 01 (1) 'Modification' │ │ │ │ +A1440 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A1444 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A1446 Length 000B (11) │ │ │ │ +A1448 Version 01 (1) │ │ │ │ +A1449 UID Size 04 (4) │ │ │ │ +A144A UID 00000000 (0) │ │ │ │ +A144E GID Size 04 (4) │ │ │ │ +A144F GID 00000000 (0) │ │ │ │ + │ │ │ │ +A1453 CENTRAL HEADER #87 02014B50 (33639248) │ │ │ │ +A1457 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A1458 Created OS 03 (3) 'Unix' │ │ │ │ +A1459 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +A145A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A145B General Purpose Flag 0000 (0) │ │ │ │ +A145D Compression Method 0000 (0) 'Stored' │ │ │ │ +A145F Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A1463 CRC 42E340AB (1122189483) │ │ │ │ +A1467 Compressed Size 00001F2E (7982) │ │ │ │ +A146B Uncompressed Size 00001F2E (7982) │ │ │ │ +A146F Filename Length 001E (30) │ │ │ │ +A1471 Extra Length 0018 (24) │ │ │ │ +A1473 Comment Length 0000 (0) │ │ │ │ +A1475 Disk Start 0000 (0) │ │ │ │ +A1477 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A1479 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A147D Local Header Offset 0009689E (616606) │ │ │ │ +A1481 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA1481: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A149F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A14A1 Length 0005 (5) │ │ │ │ +A14A3 Flags 01 (1) 'Modification' │ │ │ │ +A14A4 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A14A8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A14AA Length 000B (11) │ │ │ │ +A14AC Version 01 (1) │ │ │ │ +A14AD UID Size 04 (4) │ │ │ │ +A14AE UID 00000000 (0) │ │ │ │ +A14B2 GID Size 04 (4) │ │ │ │ +A14B3 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A14B7 CENTRAL HEADER #88 02014B50 (33639248) │ │ │ │ +A14BB Created Zip Spec 3D (61) '6.1' │ │ │ │ +A14BC Created OS 03 (3) 'Unix' │ │ │ │ +A14BD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A14BE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A14BF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A14C1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A14C3 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A14C7 CRC 2D9B7122 (765161762) │ │ │ │ +A14CB Compressed Size 00004099 (16537) │ │ │ │ +A14CF Uncompressed Size 0001971B (104219) │ │ │ │ +A14D3 Filename Length 001A (26) │ │ │ │ +A14D5 Extra Length 0018 (24) │ │ │ │ +A14D7 Comment Length 0000 (0) │ │ │ │ +A14D9 Disk Start 0000 (0) │ │ │ │ +A14DB Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A14DD Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A14E1 Local Header Offset 00098824 (624676) │ │ │ │ +A14E5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA14E5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A14FF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A1501 Length 0005 (5) │ │ │ │ +A1503 Flags 01 (1) 'Modification' │ │ │ │ +A1504 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A1508 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A150A Length 000B (11) │ │ │ │ +A150C Version 01 (1) │ │ │ │ +A150D UID Size 04 (4) │ │ │ │ +A150E UID 00000000 (0) │ │ │ │ +A1512 GID Size 04 (4) │ │ │ │ +A1513 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A1517 CENTRAL HEADER #89 02014B50 (33639248) │ │ │ │ +A151B Created Zip Spec 3D (61) '6.1' │ │ │ │ +A151C Created OS 03 (3) 'Unix' │ │ │ │ +A151D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A151E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A151F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A1521 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A1523 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A1527 CRC 0819CE01 (135908865) │ │ │ │ +A152B Compressed Size 000029D0 (10704) │ │ │ │ +A152F Uncompressed Size 0000BB3A (47930) │ │ │ │ +A1533 Filename Length 0018 (24) │ │ │ │ +A1535 Extra Length 0018 (24) │ │ │ │ +A1537 Comment Length 0000 (0) │ │ │ │ +A1539 Disk Start 0000 (0) │ │ │ │ +A153B Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A153D Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A1541 Local Header Offset 0009C911 (641297) │ │ │ │ +A1545 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA1545: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A155D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A155F Length 0005 (5) │ │ │ │ +A1561 Flags 01 (1) 'Modification' │ │ │ │ +A1562 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A1566 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A1568 Length 000B (11) │ │ │ │ +A156A Version 01 (1) │ │ │ │ +A156B UID Size 04 (4) │ │ │ │ +A156C UID 00000000 (0) │ │ │ │ +A1570 GID Size 04 (4) │ │ │ │ +A1571 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A1575 CENTRAL HEADER #90 02014B50 (33639248) │ │ │ │ +A1579 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A157A Created OS 03 (3) 'Unix' │ │ │ │ +A157B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A157C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A157D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A157F Compression Method 0008 (8) 'Deflated' │ │ │ │ +A1581 Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A1585 CRC DCB3B516 (3702764822) │ │ │ │ +A1589 Compressed Size 000000AE (174) │ │ │ │ +A158D Uncompressed Size 000000FC (252) │ │ │ │ +A1591 Filename Length 0016 (22) │ │ │ │ +A1593 Extra Length 0018 (24) │ │ │ │ +A1595 Comment Length 0000 (0) │ │ │ │ +A1597 Disk Start 0000 (0) │ │ │ │ +A1599 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A159B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A159F Local Header Offset 0009F333 (652083) │ │ │ │ +A15A3 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA15A3: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A15B9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A15BB Length 0005 (5) │ │ │ │ +A15BD Flags 01 (1) 'Modification' │ │ │ │ +A15BE Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A15C2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A15C4 Length 000B (11) │ │ │ │ +A15C6 Version 01 (1) │ │ │ │ +A15C7 UID Size 04 (4) │ │ │ │ +A15C8 UID 00000000 (0) │ │ │ │ +A15CC GID Size 04 (4) │ │ │ │ +A15CD GID 00000000 (0) │ │ │ │ + │ │ │ │ +A15D1 CENTRAL HEADER #91 02014B50 (33639248) │ │ │ │ +A15D5 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A15D6 Created OS 03 (3) 'Unix' │ │ │ │ +A15D7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A15D8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A15D9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A15DB Compression Method 0008 (8) 'Deflated' │ │ │ │ +A15DD Modification Time 5CAABDEA (1554693610) 'Sun May 10 23:47:20 2026' │ │ │ │ +A15E1 CRC 58439733 (1480824627) │ │ │ │ +A15E5 Compressed Size 00000077 (119) │ │ │ │ +A15E9 Uncompressed Size 000000A2 (162) │ │ │ │ +A15ED Filename Length 002D (45) │ │ │ │ +A15EF Extra Length 0018 (24) │ │ │ │ +A15F1 Comment Length 0000 (0) │ │ │ │ +A15F3 Disk Start 0000 (0) │ │ │ │ +A15F5 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A15F7 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A15FB Local Header Offset 0009F431 (652337) │ │ │ │ +A15FF Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA15FF: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A162C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A162E Length 0005 (5) │ │ │ │ +A1630 Flags 01 (1) 'Modification' │ │ │ │ +A1631 Modification Time 6A011909 (1778456841) 'Sun May 10 23:47:21 2026' │ │ │ │ +A1635 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A1637 Length 000B (11) │ │ │ │ +A1639 Version 01 (1) │ │ │ │ +A163A UID Size 04 (4) │ │ │ │ +A163B UID 00000000 (0) │ │ │ │ +A163F GID Size 04 (4) │ │ │ │ +A1640 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A1644 END CENTRAL HEADER 06054B50 (101010256) │ │ │ │ +A1648 Number of this disk 0000 (0) │ │ │ │ +A164A Central Dir Disk no 0000 (0) │ │ │ │ +A164C Entries in this disk 005B (91) │ │ │ │ +A164E Total Entries 005B (91) │ │ │ │ +A1650 Size of Central Dir 00002135 (8501) │ │ │ │ +A1654 Offset to Central Dir 0009F50F (652559) │ │ │ │ +A1658 Comment Length 0000 (0) │ │ │ │ # │ │ │ │ # Warning Count: 182 │ │ │ │ # │ │ │ │ # Done │ │ │ ├── filetype from file(1) │ │ │ │ @@ -1 +1 @@ │ │ │ │ -Zip archive data, made by v6.1 UNIX, extract using at least v1.0, last modified May 05 2026 13:52:08, uncompressed size 20, method=store │ │ │ │ +Zip archive data, made by v6.1 UNIX, extract using at least v1.0, last modified May 10 2026 23:47:20, uncompressed size 20, method=store │ │ │ ├── OEBPS/typespec.xhtml │ │ │ │ @@ -143,122 +143,122 @@ │ │ │ │ and optional (=>) association types. If an association type is mandatory, an │ │ │ │ association with that type needs to be present. In the case of an optional │ │ │ │ association type it is not required for the key type to be present.

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

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

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

Table: Built-in types, predefined aliases

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

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

Table: Additional built-in types

Note

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

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

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

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

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

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

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

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

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

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

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

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

│ │ │ │

Change

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

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

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

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

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

-type gadget() :: #{}.

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

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

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

-type gadget() :: #{}.

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

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

│ │ │ │

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

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

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

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

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

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

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

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

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

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

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

Example:

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

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

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

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

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

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

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

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

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

Example:

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

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

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

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

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

Read more on Opaques

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

│ │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

Change

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

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

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

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

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

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

#rec{}

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

#rec{some_field :: Type}

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

Note

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

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

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

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

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

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

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

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

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

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

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

│ │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

-spec id(X) -> X.

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

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

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

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

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

Note

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Note

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

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

is equivalent to:

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

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

DO

ets:delete(Tab, Key),

DO NOT

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

DO

ets:delete(Tab, Key),

DO NOT

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

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

│ │ │ │

Do not fetch data that you already have.

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

Note

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

DO

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

DO NOT

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

DO NOT

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

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

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

│ │ │ │

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

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

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

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

DO

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

DO

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

DO NOT

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

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

DO

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

DO NOT

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

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

DO

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

DO NOT

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

DO NOT

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

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

DO

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

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

DO

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

DO NOT

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

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

DO NOT

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

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

│ │ │ │

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

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

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

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

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

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

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

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

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

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

│ │ │ │

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

Example:

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

Example:

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

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

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

│ │ │ │

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

Example:

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

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

│ │ │ │

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

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

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

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

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

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

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

│ │ │ │ -

This is the type definition for the supervisor flags:

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

This is the type definition for the supervisor flags:

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

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

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

│ │ │ │ -

The type definition for a child specification is as follows:

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

    The id key is mandatory.

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

    The type definition for a child specification is as follows:

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

      The id key is mandatory.

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

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

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

      The start key is mandatory.

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

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

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

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

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

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

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

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

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

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

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

      or simplified, relying on the default values:

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

      or simplified, relying on the default values:

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

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

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

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

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

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

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

      Example: A child specification to start another supervisor:

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

      Example: A child specification to start another supervisor:

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

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

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

      │ │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      │ │ │ │

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

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

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

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

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

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

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

      For example, adding a child to simple_sup above:

      supervisor:start_child(Pid, [id1])

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

      call:start_link(id1)

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

      supervisor:terminate_child(Sup, Pid)

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      The code is explained in the next sections.

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

      │ │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      callback_mode() ->
      │ │ │ │      state_functions.

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

      │ │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      │ │ │ │

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

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

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

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

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

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

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

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

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

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

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

      │ │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

      │ │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

      │ │ │ │

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

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

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

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

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

      │ │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      │ │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      │ │ │ │ -

      Using state functions:

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

      Using state functions:

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

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

      │ │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      │ │ │ │

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

      Note

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

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

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

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

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

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

      │ │ │ │

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

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

      Erlang/OTP 27 [erts-15.0] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
      │ │ │ │  
      │ │ │ │ -Eshell V15.0 (press Ctrl+G to abort, type help(). for help)
      │ │ │ │ -1> code_lock:start_link([1,2,3,4]).
      │ │ │ │ +Eshell V15.0 (press Ctrl+G to abort, type help(). for help)
      │ │ │ │ +1> code_lock:start_link([1,2,3,4]).
      │ │ │ │  Lock
      │ │ │ │ -{ok,<0.90.0>}
      │ │ │ │ -2> sys:statistics(code_lock, true).
      │ │ │ │ +{ok,<0.90.0>}
      │ │ │ │ +2> sys:statistics(code_lock, true).
      │ │ │ │  ok
      │ │ │ │ -3> sys:trace(code_lock, true).
      │ │ │ │ +3> sys:trace(code_lock, true).
      │ │ │ │  ok
      │ │ │ │ -4> code_lock:button(1).
      │ │ │ │ -*DBG* code_lock receive cast {button,1} in state locked
      │ │ │ │ +4> code_lock:button(1).
      │ │ │ │ +*DBG* code_lock receive cast {button,1} in state locked
      │ │ │ │  ok
      │ │ │ │ -*DBG* code_lock consume cast {button,1} in state locked
      │ │ │ │ -5> code_lock:button(2).
      │ │ │ │ -*DBG* code_lock receive cast {button,2} in state locked
      │ │ │ │ +*DBG* code_lock consume cast {button,1} in state locked
      │ │ │ │ +5> code_lock:button(2).
      │ │ │ │ +*DBG* code_lock receive cast {button,2} in state locked
      │ │ │ │  ok
      │ │ │ │ -*DBG* code_lock consume cast {button,2} in state locked
      │ │ │ │ -6> code_lock:button(3).
      │ │ │ │ -*DBG* code_lock receive cast {button,3} in state locked
      │ │ │ │ +*DBG* code_lock consume cast {button,2} in state locked
      │ │ │ │ +6> code_lock:button(3).
      │ │ │ │ +*DBG* code_lock receive cast {button,3} in state locked
      │ │ │ │  ok
      │ │ │ │ -*DBG* code_lock consume cast {button,3} in state locked
      │ │ │ │ -7> code_lock:button(4).
      │ │ │ │ -*DBG* code_lock receive cast {button,4} in state locked
      │ │ │ │ +*DBG* code_lock consume cast {button,3} in state locked
      │ │ │ │ +7> code_lock:button(4).
      │ │ │ │ +*DBG* code_lock receive cast {button,4} in state locked
      │ │ │ │  ok
      │ │ │ │  Unlock
      │ │ │ │ -*DBG* code_lock consume cast {button,4} in state locked => open
      │ │ │ │ -*DBG* code_lock start_timer {state_timeout,10000,lock,[]} in state open
      │ │ │ │ +*DBG* code_lock consume cast {button,4} in state locked => open
      │ │ │ │ +*DBG* code_lock start_timer {state_timeout,10000,lock,[]} in state open
      │ │ │ │  *DBG* code_lock receive state_timeout lock in state open
      │ │ │ │  Lock
      │ │ │ │  *DBG* code_lock consume state_timeout lock in state open => locked
      │ │ │ │ -8> sys:statistics(code_lock, get).
      │ │ │ │ -{ok,[{start_time,{{2024,5,3},{8,11,1}}},
      │ │ │ │ -     {current_time,{{2024,5,3},{8,11,48}}},
      │ │ │ │ -     {reductions,4098},
      │ │ │ │ -     {messages_in,5},
      │ │ │ │ -     {messages_out,0}]}
      │ │ │ │ -9> sys:statistics(code_lock, false).
      │ │ │ │ -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 │ │ │ │ @@ -232,74 +232,74 @@ │ │ │ │ │ │ │ │ │ │ │ │ Application Upgrade File │ │ │ │

          │ │ │ │

          To define how to upgrade/downgrade between the current version and previous │ │ │ │ versions of an application, an application upgrade file, or in short │ │ │ │ .appup file is created. The file is to be called Application.appup, where │ │ │ │ -Application is the application name:

          {Vsn,
          │ │ │ │ - [{UpFromVsn1, InstructionsU1},
          │ │ │ │ +Application is the application name:

          {Vsn,
          │ │ │ │ + [{UpFromVsn1, InstructionsU1},
          │ │ │ │    ...,
          │ │ │ │ -  {UpFromVsnK, InstructionsUK}],
          │ │ │ │ - [{DownToVsn1, InstructionsD1},
          │ │ │ │ +  {UpFromVsnK, InstructionsUK}],
          │ │ │ │ + [{DownToVsn1, InstructionsD1},
          │ │ │ │    ...,
          │ │ │ │ -  {DownToVsnK, InstructionsDK}]}.
          • Vsn, a string, is the current version of the application, as defined in the │ │ │ │ + {DownToVsnK, InstructionsDK}]}.
          • Vsn, a string, is the current version of the application, as defined in the │ │ │ │ .app file.
          • Each UpFromVsn is a previous version of the application to upgrade from.
          • Each DownToVsn is a previous version of the application to downgrade to.
          • Each Instructions is a list of release handling instructions.

          UpFromVsn and DownToVsn can also be specified as regular expressions. For │ │ │ │ more information about the syntax and contents of the .appup file, see │ │ │ │ appup in SASL.

          Appup Cookbook includes examples of .appup files for │ │ │ │ typical upgrade/downgrade cases.

          Example: Consider the release ch_rel-1 from │ │ │ │ Releases. Assume you want to add a function │ │ │ │ available/0 to server ch3, which returns the number of available channels │ │ │ │ (when trying out the example, make the change in a copy of the original │ │ │ │ -directory, to ensure that the first version is still available):

          -module(ch3).
          │ │ │ │ --behaviour(gen_server).
          │ │ │ │ +directory, to ensure that the first version is still available):

          -module(ch3).
          │ │ │ │ +-behaviour(gen_server).
          │ │ │ │  
          │ │ │ │ --export([start_link/0]).
          │ │ │ │ --export([alloc/0, free/1]).
          │ │ │ │ --export([available/0]).
          │ │ │ │ --export([init/1, handle_call/3, handle_cast/2]).
          │ │ │ │ +-export([start_link/0]).
          │ │ │ │ +-export([alloc/0, free/1]).
          │ │ │ │ +-export([available/0]).
          │ │ │ │ +-export([init/1, handle_call/3, handle_cast/2]).
          │ │ │ │  
          │ │ │ │ -start_link() ->
          │ │ │ │ -    gen_server:start_link({local, ch3}, ch3, [], []).
          │ │ │ │ +start_link() ->
          │ │ │ │ +    gen_server:start_link({local, ch3}, ch3, [], []).
          │ │ │ │  
          │ │ │ │ -alloc() ->
          │ │ │ │ -    gen_server:call(ch3, alloc).
          │ │ │ │ +alloc() ->
          │ │ │ │ +    gen_server:call(ch3, alloc).
          │ │ │ │  
          │ │ │ │ -free(Ch) ->
          │ │ │ │ -    gen_server:cast(ch3, {free, Ch}).
          │ │ │ │ +free(Ch) ->
          │ │ │ │ +    gen_server:cast(ch3, {free, Ch}).
          │ │ │ │  
          │ │ │ │ -available() ->
          │ │ │ │ -    gen_server:call(ch3, available).
          │ │ │ │ +available() ->
          │ │ │ │ +    gen_server:call(ch3, available).
          │ │ │ │  
          │ │ │ │ -init(_Args) ->
          │ │ │ │ -    {ok, channels()}.
          │ │ │ │ +init(_Args) ->
          │ │ │ │ +    {ok, channels()}.
          │ │ │ │  
          │ │ │ │ -handle_call(alloc, _From, Chs) ->
          │ │ │ │ -    {Ch, Chs2} = alloc(Chs),
          │ │ │ │ -    {reply, Ch, Chs2};
          │ │ │ │ -handle_call(available, _From, Chs) ->
          │ │ │ │ -    N = available(Chs),
          │ │ │ │ -    {reply, N, Chs}.
          │ │ │ │ +handle_call(alloc, _From, Chs) ->
          │ │ │ │ +    {Ch, Chs2} = alloc(Chs),
          │ │ │ │ +    {reply, Ch, Chs2};
          │ │ │ │ +handle_call(available, _From, Chs) ->
          │ │ │ │ +    N = available(Chs),
          │ │ │ │ +    {reply, N, Chs}.
          │ │ │ │  
          │ │ │ │ -handle_cast({free, Ch}, Chs) ->
          │ │ │ │ -    Chs2 = free(Ch, Chs),
          │ │ │ │ -    {noreply, Chs2}.

          A new version of the ch_app.app file must now be created, where the version is │ │ │ │ -updated:

          {application, ch_app,
          │ │ │ │ - [{description, "Channel allocator"},
          │ │ │ │ -  {vsn, "2"},
          │ │ │ │ -  {modules, [ch_app, ch_sup, ch3]},
          │ │ │ │ -  {registered, [ch3]},
          │ │ │ │ -  {applications, [kernel, stdlib, sasl]},
          │ │ │ │ -  {mod, {ch_app,[]}}
          │ │ │ │ - ]}.

          To upgrade ch_app from "1" to "2" (and to downgrade from "2" to "1"), │ │ │ │ +handle_cast({free, Ch}, Chs) -> │ │ │ │ + Chs2 = free(Ch, Chs), │ │ │ │ + {noreply, Chs2}.

          A new version of the ch_app.app file must now be created, where the version is │ │ │ │ +updated:

          {application, ch_app,
          │ │ │ │ + [{description, "Channel allocator"},
          │ │ │ │ +  {vsn, "2"},
          │ │ │ │ +  {modules, [ch_app, ch_sup, ch3]},
          │ │ │ │ +  {registered, [ch3]},
          │ │ │ │ +  {applications, [kernel, stdlib, sasl]},
          │ │ │ │ +  {mod, {ch_app,[]}}
          │ │ │ │ + ]}.

          To upgrade ch_app from "1" to "2" (and to downgrade from "2" to "1"), │ │ │ │ you only need to load the new (old) version of the ch3 callback module. Create │ │ │ │ -the application upgrade file ch_app.appup in the ebin directory:

          {"2",
          │ │ │ │ - [{"1", [{load_module, ch3}]}],
          │ │ │ │ - [{"1", [{load_module, ch3}]}]
          │ │ │ │ -}.

          │ │ │ │ +the application upgrade file ch_app.appup in the ebin directory:

          {"2",
          │ │ │ │ + [{"1", [{load_module, ch3}]}],
          │ │ │ │ + [{"1", [{load_module, ch3}]}]
          │ │ │ │ +}.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Release Upgrade File │ │ │ │

          │ │ │ │

          To define how to upgrade/downgrade between the new version and previous versions │ │ │ │ of a release, a release upgrade file, or in short .relup file, is to be │ │ │ │ @@ -310,22 +310,22 @@ │ │ │ │ are to be added and deleted, and which applications that must be upgraded and/or │ │ │ │ downgraded. The instructions for this are fetched from the .appup files and │ │ │ │ transformed into a single list of low-level instructions in the right order.

          If the relup file is relatively simple, it can be created manually. It is only │ │ │ │ to contain low-level instructions.

          For details about the syntax and contents of the release upgrade file, see │ │ │ │ relup in SASL.

          Example, continued from the previous section: You have a new version "2" of │ │ │ │ ch_app and an .appup file. A new version of the .rel file is also needed. │ │ │ │ This time the file is called ch_rel-2.rel and the release version string is │ │ │ │ -changed from "A" to "B":

          {release,
          │ │ │ │ - {"ch_rel", "B"},
          │ │ │ │ - {erts, "14.2.5"},
          │ │ │ │ - [{kernel, "9.2.4"},
          │ │ │ │ -  {stdlib, "5.2.3"},
          │ │ │ │ -  {sasl, "4.2.1"},
          │ │ │ │ -  {ch_app, "2"}]
          │ │ │ │ -}.

          Now the relup file can be generated:

          1> systools:make_relup("ch_rel-2", ["ch_rel-1"], ["ch_rel-1"]).
          │ │ │ │ +changed from "A" to "B":

          {release,
          │ │ │ │ + {"ch_rel", "B"},
          │ │ │ │ + {erts, "14.2.5"},
          │ │ │ │ + [{kernel, "9.2.4"},
          │ │ │ │ +  {stdlib, "5.2.3"},
          │ │ │ │ +  {sasl, "4.2.1"},
          │ │ │ │ +  {ch_app, "2"}]
          │ │ │ │ +}.

          Now the relup file can be generated:

          1> systools:make_relup("ch_rel-2", ["ch_rel-1"], ["ch_rel-1"]).
          │ │ │ │  ok

          This generates a relup file with instructions for how to upgrade from version │ │ │ │ "A" ("ch_rel-1") to version "B" ("ch_rel-2") and how to downgrade from version │ │ │ │ "B" to version "A".

          Both the old and new versions of the .app and .rel files must be in the code │ │ │ │ path, as well as the .appup and (new) .beam files. The code path can be │ │ │ │ extended by using the option path:

          1> systools:make_relup("ch_rel-2", ["ch_rel-1"], ["ch_rel-1"],
          │ │ │ │  [{path,["../ch_rel-1",
          │ │ │ │  "../ch_rel-1/lib/ch_app-1/ebin"]}]).
          │ │ │ │ @@ -338,25 +338,25 @@
          │ │ │ │  

          When you have made a new version of a release, a release package can be created │ │ │ │ with this new version and transferred to the target environment.

          To install the new version of the release in runtime, the release │ │ │ │ handler is used. This is a process belonging to the SASL application, │ │ │ │ which handles unpacking, installation, and removal of release │ │ │ │ packages. The release_handler module communicates with this process.

          Assuming there is an operational target system with installation root directory │ │ │ │ $ROOT, the release package with the new version of the release is to be copied │ │ │ │ to $ROOT/releases.

          First, unpack the release package. The files are then extracted from the │ │ │ │ -package:

          release_handler:unpack_release(ReleaseName) => {ok, Vsn}
          • ReleaseName is the name of the release package except the .tar.gz │ │ │ │ +package:

            release_handler:unpack_release(ReleaseName) => {ok, Vsn}
            • ReleaseName is the name of the release package except the .tar.gz │ │ │ │ extension.
            • Vsn is the version of the unpacked release, as defined in its .rel file.

            A directory $ROOT/lib/releases/Vsn is created, where the .rel file, the boot │ │ │ │ script start.boot, the system configuration file sys.config, and relup are │ │ │ │ placed. For applications with new version numbers, the application directories │ │ │ │ are placed under $ROOT/lib. Unchanged applications are not affected.

            An unpacked release can be installed. The release handler then evaluates the │ │ │ │ -instructions in relup, step by step:

            release_handler:install_release(Vsn) => {ok, FromVsn, []}

            If an error occurs during the installation, the system is rebooted using the old │ │ │ │ +instructions in relup, step by step:

            release_handler:install_release(Vsn) => {ok, FromVsn, []}

            If an error occurs during the installation, the system is rebooted using the old │ │ │ │ version of the release. If installation succeeds, the system is afterwards using │ │ │ │ the new version of the release, but if anything happens and the system is │ │ │ │ rebooted, it starts using the previous version again.

            To be made the default version, the newly installed release must be made │ │ │ │ permanent, which means the previous version becomes old:

            release_handler:make_permanent(Vsn) => ok

            The system keeps information about which versions are old and permanent in the │ │ │ │ -files $ROOT/releases/RELEASES and $ROOT/releases/start_erl.data.

            To downgrade from Vsn to FromVsn, install_release must be called again:

            release_handler:install_release(FromVsn) => {ok, Vsn, []}

            An installed, but not permanent, release can be removed. Information about the │ │ │ │ +files $ROOT/releases/RELEASES and $ROOT/releases/start_erl.data.

            To downgrade from Vsn to FromVsn, install_release must be called again:

            release_handler:install_release(FromVsn) => {ok, Vsn, []}

            An installed, but not permanent, release can be removed. Information about the │ │ │ │ release is then deleted from $ROOT/releases/RELEASES and the release-specific │ │ │ │ code, that is, the new application directories and the $ROOT/releases/Vsn │ │ │ │ directory, are removed.

            release_handler:remove_release(Vsn) => ok

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Example (continued from the previous sections) │ │ │ │ @@ -367,17 +367,17 @@ │ │ │ │ is needed, the file is to contain the empty list:

            [].

            Step 2) Start the system as a simple target system. In reality, it is to be │ │ │ │ started as an embedded system. However, using erl with the correct boot script │ │ │ │ and config file is enough for illustration purposes:

            % cd $ROOT
            │ │ │ │  % bin/erl -boot $ROOT/releases/A/start -config $ROOT/releases/A/sys
            │ │ │ │  ...

            $ROOT is the installation directory of the target system.

            Step 3) In another Erlang shell, generate start scripts and create a release │ │ │ │ package for the new version "B". Remember to include (a possible updated) │ │ │ │ sys.config and the relup file. For more information, see │ │ │ │ -Release Upgrade File.

            1> systools:make_script("ch_rel-2").
            │ │ │ │ +Release Upgrade File.

            1> systools:make_script("ch_rel-2").
            │ │ │ │  ok
            │ │ │ │ -2> systools:make_tar("ch_rel-2").
            │ │ │ │ +2> systools:make_tar("ch_rel-2").
            │ │ │ │  ok

            The new release package now also contains version "2" of ch_app and the │ │ │ │ relup file:

            % tar tf ch_rel-2.tar
            │ │ │ │  lib/kernel-9.2.4/ebin/kernel.app
            │ │ │ │  lib/kernel-9.2.4/ebin/application.beam
            │ │ │ │  ...
            │ │ │ │  lib/stdlib-5.2.3/ebin/stdlib.app
            │ │ │ │  lib/stdlib-5.2.3/ebin/argparse.beam
            │ │ │ │ @@ -390,31 +390,31 @@
            │ │ │ │  lib/ch_app-2/ebin/ch_sup.beam
            │ │ │ │  lib/ch_app-2/ebin/ch3.beam
            │ │ │ │  releases/B/start.boot
            │ │ │ │  releases/B/relup
            │ │ │ │  releases/B/sys.config
            │ │ │ │  releases/B/ch_rel-2.rel
            │ │ │ │  releases/ch_rel-2.rel

            Step 4) Copy the release package ch_rel-2.tar.gz to the $ROOT/releases │ │ │ │ -directory.

            Step 5) In the running target system, unpack the release package:

            1> release_handler:unpack_release("ch_rel-2").
            │ │ │ │ -{ok,"B"}

            The new application version ch_app-2 is installed under $ROOT/lib next to │ │ │ │ +directory.

            Step 5) In the running target system, unpack the release package:

            1> release_handler:unpack_release("ch_rel-2").
            │ │ │ │ +{ok,"B"}

            The new application version ch_app-2 is installed under $ROOT/lib next to │ │ │ │ ch_app-1. The kernel, stdlib, and sasl directories are not affected, as │ │ │ │ they have not changed.

            Under $ROOT/releases, a new directory B is created, containing │ │ │ │ -ch_rel-2.rel, start.boot, sys.config, and relup.

            Step 6) Check if the function ch3:available/0 is available:

            2> ch3:available().
            │ │ │ │ +ch_rel-2.rel, start.boot, sys.config, and relup.

            Step 6) Check if the function ch3:available/0 is available:

            2> ch3:available().
            │ │ │ │  ** exception error: undefined function ch3:available/0

            Step 7) Install the new release. The instructions in $ROOT/releases/B/relup │ │ │ │ are executed one by one, resulting in the new version of ch3 being loaded. The │ │ │ │ -function ch3:available/0 is now available:

            3> release_handler:install_release("B").
            │ │ │ │ -{ok,"A",[]}
            │ │ │ │ -4> ch3:available().
            │ │ │ │ +function ch3:available/0 is now available:

            3> release_handler:install_release("B").
            │ │ │ │ +{ok,"A",[]}
            │ │ │ │ +4> ch3:available().
            │ │ │ │  3
            │ │ │ │ -5> code:which(ch3).
            │ │ │ │ +5> code:which(ch3).
            │ │ │ │  ".../lib/ch_app-2/ebin/ch3.beam"
            │ │ │ │ -6> code:which(ch_sup).
            │ │ │ │ +6> code:which(ch_sup).
            │ │ │ │  ".../lib/ch_app-1/ebin/ch_sup.beam"

            Processes in ch_app for which code have not been updated, for example, the │ │ │ │ supervisor, are still evaluating code from ch_app-1.

            Step 8) If the target system is now rebooted, it uses version "A" again. The │ │ │ │ -"B" version must be made permanent, to be used when the system is rebooted.

            7> release_handler:make_permanent("B").
            │ │ │ │ +"B" version must be made permanent, to be used when the system is rebooted.

            7> release_handler:make_permanent("B").
            │ │ │ │  ok

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Updating Application Specifications │ │ │ │

            │ │ │ │

            When a new version of a release is installed, the application specifications are │ │ │ │ @@ -423,14 +423,14 @@ │ │ │ │ boot script is generated from the same .rel file as is used to build the │ │ │ │ release package itself.

            Specifically, the application configuration parameters are automatically updated │ │ │ │ according to (in increasing priority order):

            • The data in the boot script, fetched from the new application resource file │ │ │ │ App.app
            • The new sys.config
            • Command-line arguments -App Par Val

            This means that parameter values set in the other system configuration files and │ │ │ │ values set using application:set_env/3 are disregarded.

            When an installed release is made permanent, the system process init is set to │ │ │ │ point out the new sys.config.

            After the installation, the application controller compares the old and new │ │ │ │ configuration parameters for all running applications and call the callback │ │ │ │ -function:

            Module:config_change(Changed, New, Removed)
            • Module is the application callback module as defined by the mod key in the │ │ │ │ +function:

              Module:config_change(Changed, New, Removed)
              • Module is the application callback module as defined by the mod key in the │ │ │ │ .app file.
              • Changed and New are lists of {Par,Val} for all changed and added │ │ │ │ configuration parameters, respectively.
              • Removed is a list of all parameters Par that have been removed.

              The function is optional and can be omitted when implementing an application │ │ │ │ callback module.

              │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/ref_man_records.xhtml │ │ │ │ @@ -28,17 +28,17 @@ │ │ │ │ │ │ │ │ │ │ │ │ Defining Records │ │ │ │

            │ │ │ │

            A record definition consists of the name of the record, followed by the field │ │ │ │ names of the record. Record and field names must be atoms. Each field can be │ │ │ │ given an optional default value. If no default value is supplied, undefined is │ │ │ │ -used.

            -record(Name, {Field1 [= Expr1],
            │ │ │ │ +used.

            -record(Name, {Field1 [= Expr1],
            │ │ │ │                 ...
            │ │ │ │ -               FieldN [= ExprN]}).

            The default value for a field is an arbitrary expression, except that it must │ │ │ │ + FieldN [= ExprN]}).

            The default value for a field is an arbitrary expression, except that it must │ │ │ │ not use any variables.

            A record definition can be placed anywhere among the attributes and function │ │ │ │ declarations of a module, but the definition must come before any usage of the │ │ │ │ record.

            If a record is used in several modules, it is recommended that the record │ │ │ │ definition is placed in an include file.

            Change

            Starting from Erlang/OTP 26, records can be defined in the Erlang shell │ │ │ │ using the syntax described in this section. In earlier releases, it was │ │ │ │ necessary to use the shell built-in function rd/2.

            │ │ │ │ │ │ │ │ @@ -48,32 +48,32 @@ │ │ │ │

            │ │ │ │

            The following expression creates a new Name record where the value of each │ │ │ │ field FieldI is the value of evaluating the corresponding expression ExprI:

            #Name{Field1=Expr1, ..., FieldK=ExprK}

            The fields can be in any order, not necessarily the same order as in the record │ │ │ │ definition, and fields can be omitted. Omitted fields get their respective │ │ │ │ default value instead.

            If several fields are to be assigned the same value, the following construction │ │ │ │ can be used:

            #Name{Field1=Expr1, ..., FieldK=ExprK, _=ExprL}

            Omitted fields then get the value of evaluating ExprL instead of their default │ │ │ │ values. This feature is primarily intended to be used to create patterns for ETS │ │ │ │ -and Mnesia match functions.

            Example:

            -record(person, {name, phone, address}).
            │ │ │ │ +and Mnesia match functions.

            Example:

            -record(person, {name, phone, address}).
            │ │ │ │  
            │ │ │ │ -lookup(Name, Tab) ->
            │ │ │ │ -    ets:match_object(Tab, #person{name=Name, _='_'}).

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

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Accessing Record Fields │ │ │ │

            │ │ │ │
            Expr#Name.Field

            Returns the value of the specified field. Expr is to evaluate to a Name │ │ │ │ -record.

            Example:

            -record(person, {name, phone, address}).
            │ │ │ │ +record.

            Example:

            -record(person, {name, phone, address}).
            │ │ │ │  
            │ │ │ │ -get_person_name(Person) ->
            │ │ │ │ +get_person_name(Person) ->
            │ │ │ │      Person#person.name.

            The following expression returns the position of the specified field in the │ │ │ │ -tuple representation of the record:

            #Name.Field

            Example:

            -record(person, {name, phone, address}).
            │ │ │ │ +tuple representation of the record:

            #Name.Field

            Example:

            -record(person, {name, phone, address}).
            │ │ │ │  
            │ │ │ │ -lookup(Name, List) ->
            │ │ │ │ -    lists:keyfind(Name, #person.name, List).

            │ │ │ │ +lookup(Name, List) -> │ │ │ │ + lists:keyfind(Name, #person.name, List).

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Updating Records │ │ │ │

            │ │ │ │
            Expr#Name{Field1=Expr1, ..., FieldK=ExprK}

            Expr is to evaluate to a Name record. A copy of this record is returned, │ │ │ │ with the value of each specified field FieldI changed to the value of │ │ │ │ @@ -83,48 +83,48 @@ │ │ │ │ │ │ │ │ │ │ │ │ Records in Guards │ │ │ │ │ │ │ │

            Since record expressions are expanded to tuple expressions, creating │ │ │ │ records and accessing record fields are allowed in guards. However, │ │ │ │ all subexpressions (for initializing fields), must be valid guard │ │ │ │ -expressions as well.

            Examples:

            handle(Msg, State) when Msg =:= #msg{to=void, no=3} ->
            │ │ │ │ +expressions as well.

            Examples:

            handle(Msg, State) when Msg =:= #msg{to=void, no=3} ->
            │ │ │ │      ...
            │ │ │ │  
            │ │ │ │ -handle(Msg, State) when State#state.running =:= true ->
            │ │ │ │ -    ...

            There is also a type test BIF is_record(Term, RecordTag).

            Example:

            is_person(P) when is_record(P, person) ->
            │ │ │ │ +handle(Msg, State) when State#state.running =:= true ->
            │ │ │ │ +    ...

            There is also a type test BIF is_record(Term, RecordTag).

            Example:

            is_person(P) when is_record(P, person) ->
            │ │ │ │      true;
            │ │ │ │ -is_person(_P) ->
            │ │ │ │ +is_person(_P) ->
            │ │ │ │      false.

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Records in Patterns │ │ │ │

            │ │ │ │

            A pattern that matches a certain record is created in the same way as a record │ │ │ │ is created:

            #Name{Field1=Expr1, ..., FieldK=ExprK}

            In this case, one or more of Expr1 ... ExprK can be unbound variables.

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Nested Records │ │ │ │

            │ │ │ │ -

            Assume the following record definitions:

            -record(nrec0, {name = "nested0"}).
            │ │ │ │ --record(nrec1, {name = "nested1", nrec0=#nrec0{}}).
            │ │ │ │ --record(nrec2, {name = "nested2", nrec1=#nrec1{}}).
            │ │ │ │ +

            Assume the following record definitions:

            -record(nrec0, {name = "nested0"}).
            │ │ │ │ +-record(nrec1, {name = "nested1", nrec0=#nrec0{}}).
            │ │ │ │ +-record(nrec2, {name = "nested2", nrec1=#nrec1{}}).
            │ │ │ │  
            │ │ │ │ -N2 = #nrec2{},

            Accessing or updating nested records can be written without parentheses:

            "nested0" = N2#nrec2.nrec1#nrec1.nrec0#nrec0.name,
            │ │ │ │ +N2 = #nrec2{},

            Accessing or updating nested records can be written without parentheses:

            "nested0" = N2#nrec2.nrec1#nrec1.nrec0#nrec0.name,
            │ │ │ │      N0n = N2#nrec2.nrec1#nrec1.nrec0#nrec0{name = "nested0a"},

            which is equivalent to:

            "nested0" = ((N2#nrec2.nrec1)#nrec1.nrec0)#nrec0.name,
            │ │ │ │  N0n = ((N2#nrec2.nrec1)#nrec1.nrec0)#nrec0{name = "nested0a"},

            Change

            Before Erlang/OTP R14, parentheses were necessary when accessing or updating │ │ │ │ nested records.

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Internal Representation of Records │ │ │ │

            │ │ │ │

            Record expressions are translated to tuple expressions during compilation. A │ │ │ │ -record defined as:

            -record(Name, {Field1, ..., FieldN}).

            is internally represented by the tuple:

            {Name, Value1, ..., ValueN}

            Here each ValueI is the default value for FieldI.

            To each module using records, a pseudo function is added during compilation to │ │ │ │ -obtain information about records:

            record_info(fields, Record) -> [Field]
            │ │ │ │ -record_info(size, Record) -> Size

            Size is the size of the tuple representation, that is, one more than the │ │ │ │ +record defined as:

            -record(Name, {Field1, ..., FieldN}).

            is internally represented by the tuple:

            {Name, Value1, ..., ValueN}

            Here each ValueI is the default value for FieldI.

            To each module using records, a pseudo function is added during compilation to │ │ │ │ +obtain information about records:

            record_info(fields, Record) -> [Field]
            │ │ │ │ +record_info(size, Record) -> Size

            Size is the size of the tuple representation, that is, one more than the │ │ │ │ number of fields.

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/ref_man_processes.xhtml │ │ │ │ @@ -30,18 +30,18 @@ │ │ │ │ (grow and shrink dynamically) with small memory footprint, fast to create and │ │ │ │ terminate, and the scheduling overhead is low.

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Process Creation │ │ │ │

            │ │ │ │ -

            A process is created by calling spawn():

            spawn(Module, Name, Args) -> pid()
            │ │ │ │ -  Module = Name = atom()
            │ │ │ │ -  Args = [Arg1,...,ArgN]
            │ │ │ │ -    ArgI = term()

            spawn() creates a new process and returns the pid.

            The new process starts executing in Module:Name(Arg1,...,ArgN) where the │ │ │ │ +

            A process is created by calling spawn():

            spawn(Module, Name, Args) -> pid()
            │ │ │ │ +  Module = Name = atom()
            │ │ │ │ +  Args = [Arg1,...,ArgN]
            │ │ │ │ +    ArgI = term()

            spawn() creates a new process and returns the pid.

            The new process starts executing in Module:Name(Arg1,...,ArgN) where the │ │ │ │ arguments are the elements of the (possible empty) Args argument list.

            There exist a number of different spawn BIFs:

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Registered Processes │ │ │ │

            │ │ │ │

            Besides addressing a process by using its pid, there are also BIFs for │ │ │ ├── OEBPS/ref_man_functions.xhtml │ │ │ │ @@ -25,51 +25,51 @@ │ │ │ │ │ │ │ │ │ │ │ │ Function Declaration Syntax │ │ │ │ │ │ │ │

            A function declaration is a sequence of function clauses separated by │ │ │ │ semicolons, and terminated by a period (.).

            A function clause consists of a clause head and a clause body, separated by │ │ │ │ ->.

            A clause head consists of the function name, an argument list, and an optional │ │ │ │ -guard sequence beginning with the keyword when:

            Name(Pattern11,...,Pattern1N) [when GuardSeq1] ->
            │ │ │ │ +guard sequence beginning with the keyword when:

            Name(Pattern11,...,Pattern1N) [when GuardSeq1] ->
            │ │ │ │      Body1;
            │ │ │ │  ...;
            │ │ │ │ -Name(PatternK1,...,PatternKN) [when GuardSeqK] ->
            │ │ │ │ +Name(PatternK1,...,PatternKN) [when GuardSeqK] ->
            │ │ │ │      BodyK.

            The function name is an atom. Each argument is a pattern.

            The number of arguments N is the arity of the function. A function is │ │ │ │ uniquely defined by the module name, function name, and arity. That is, two │ │ │ │ functions with the same name and in the same module, but with different arities │ │ │ │ are two different functions.

            A function named f in module mod and with arity N is often denoted as │ │ │ │ mod:f/N.

            A clause body consists of a sequence of expressions separated by comma (,):

            Expr1,
            │ │ │ │  ...,
            │ │ │ │  ExprN

            Valid Erlang expressions and guard sequences are described in │ │ │ │ -Expressions.

            Example:

            fact(N) when N > 0 ->  % first clause head
            │ │ │ │ -    N * fact(N-1);     % first clause body
            │ │ │ │ +Expressions.

            Example:

            fact(N) when N > 0 ->  % first clause head
            │ │ │ │ +    N * fact(N-1);     % first clause body
            │ │ │ │  
            │ │ │ │ -fact(0) ->             % second clause head
            │ │ │ │ +fact(0) ->             % second clause head
            │ │ │ │      1.                 % second clause body

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Function Evaluation │ │ │ │

            │ │ │ │

            When a function M:F/N is called, first the code for the function is located. │ │ │ │ If the function cannot be found, an undef runtime error occurs. Notice that │ │ │ │ the function must be exported to be visible outside the module it is defined in.

            If the function is found, the function clauses are scanned sequentially until a │ │ │ │ clause is found that fulfills both of the following two conditions:

            1. The patterns in the clause head can be successfully matched against the given │ │ │ │ arguments.
            2. The guard sequence, if any, is true.

            If such a clause cannot be found, a function_clause runtime error occurs.

            If such a clause is found, the corresponding clause body is evaluated. That is, │ │ │ │ the expressions in the body are evaluated sequentially and the value of the last │ │ │ │ -expression is returned.

            Consider the function fact:

            -module(mod).
            │ │ │ │ --export([fact/1]).
            │ │ │ │ +expression is returned.

            Consider the function fact:

            -module(mod).
            │ │ │ │ +-export([fact/1]).
            │ │ │ │  
            │ │ │ │ -fact(N) when N > 0 ->
            │ │ │ │ -    N * fact(N - 1);
            │ │ │ │ -fact(0) ->
            │ │ │ │ +fact(N) when N > 0 ->
            │ │ │ │ +    N * fact(N - 1);
            │ │ │ │ +fact(0) ->
            │ │ │ │      1.

            Assume that you want to calculate the factorial for 1:

            1> mod:fact(1).

            Evaluation starts at the first clause. The pattern N is matched against │ │ │ │ argument 1. The matching succeeds and the guard (N > 0) is true, thus N is │ │ │ │ -bound to 1, and the corresponding body is evaluated:

            N * fact(N-1) => (N is bound to 1)
            │ │ │ │ -1 * fact(0)

            Now, fact(0) is called, and the function clauses are scanned │ │ │ │ +bound to 1, and the corresponding body is evaluated:

            N * fact(N-1) => (N is bound to 1)
            │ │ │ │ +1 * fact(0)

            Now, fact(0) is called, and the function clauses are scanned │ │ │ │ sequentially again. First, the pattern N is matched against 0. The │ │ │ │ matching succeeds, but the guard (N > 0) is false. Second, the │ │ │ │ pattern 0 is matched against the argument 0. The matching succeeds │ │ │ │ and the body is evaluated:

            1 * fact(0) =>
            │ │ │ │  1 * 1 =>
            │ │ │ │  1

            Evaluation has succeed and mod:fact(1) returns 1.

            If mod:fact/1 is called with a negative number as argument, no clause head │ │ │ │ matches. A function_clause runtime error occurs.

            │ │ │ │ @@ -78,17 +78,17 @@ │ │ │ │ │ │ │ │ Tail recursion │ │ │ │

            │ │ │ │

            If the last expression of a function body is a function call, a │ │ │ │ tail-recursive call is done. This is to ensure that no system │ │ │ │ resources, for example, call stack, are consumed. This means that an │ │ │ │ infinite loop using tail-recursive calls will not exhaust the call │ │ │ │ -stack and can (in principle) run forever.

            Example:

            loop(N) ->
            │ │ │ │ -    io:format("~w~n", [N]),
            │ │ │ │ -    loop(N+1).

            The earlier factorial example is a counter-example. It is not │ │ │ │ +stack and can (in principle) run forever.

            Example:

            loop(N) ->
            │ │ │ │ +    io:format("~w~n", [N]),
            │ │ │ │ +    loop(N+1).

            The earlier factorial example is a counter-example. It is not │ │ │ │ tail-recursive, since a multiplication is done on the result of the recursive │ │ │ │ call to fact(N-1).

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

            │ │ │ │ @@ -96,14 +96,14 @@ │ │ │ │ system. BIFs do things that are difficult or impossible to implement │ │ │ │ in Erlang. Most of the BIFs belong to module erlang, but there │ │ │ │ are also BIFs belonging to a few other modules, for example lists │ │ │ │ and ets.

            The most commonly used BIFs belonging to erlang are auto-imported. They do │ │ │ │ not need to be prefixed with the module name. Which BIFs that are auto-imported │ │ │ │ is specified in the erlang module in ERTS. For example, standard-type │ │ │ │ conversion BIFs like atom_to_list and BIFs allowed in guards can be called │ │ │ │ -without specifying the module name.

            Examples:

            1> tuple_size({a,b,c}).
            │ │ │ │ +without specifying the module name.

            Examples:

            1> tuple_size({a,b,c}).
            │ │ │ │  3
            │ │ │ │ -2> atom_to_list('Erlang').
            │ │ │ │ +2> atom_to_list('Erlang').
            │ │ │ │  "Erlang"
            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/records_macros.xhtml │ │ │ │ @@ -29,40 +29,40 @@ │ │ │ │ │ │ │ │

            To illustrate this, the messenger example from the previous section is divided │ │ │ │ into the following five files:

            • mess_config.hrl

              Header file for configuration data

            • mess_interface.hrl

              Interface definitions between the client and the messenger

            • user_interface.erl

              Functions for the user interface

            • mess_client.erl

              Functions for the client side of the messenger

            • mess_server.erl

              Functions for the server side of the messenger

            While doing this, the message passing interface between the shell, the client, │ │ │ │ and the server is cleaned up and is defined using records. Also, macros are │ │ │ │ introduced:

            %%%----FILE mess_config.hrl----
            │ │ │ │  
            │ │ │ │  %%% Configure the location of the server node,
            │ │ │ │ --define(server_node, messenger@super).
            │ │ │ │ +-define(server_node, messenger@super).
            │ │ │ │  
            │ │ │ │  %%%----END FILE----
            %%%----FILE mess_interface.hrl----
            │ │ │ │  
            │ │ │ │  %%% Message interface between client and server and client shell for
            │ │ │ │  %%% messenger program
            │ │ │ │  
            │ │ │ │  %%%Messages from Client to server received in server/1 function.
            │ │ │ │ --record(logon,{client_pid, username}).
            │ │ │ │ --record(message,{client_pid, to_name, message}).
            │ │ │ │ +-record(logon,{client_pid, username}).
            │ │ │ │ +-record(message,{client_pid, to_name, message}).
            │ │ │ │  %%% {'EXIT', ClientPid, Reason}  (client terminated or unreachable.
            │ │ │ │  
            │ │ │ │  %%% Messages from Server to Client, received in await_result/0 function
            │ │ │ │ --record(abort_client,{message}).
            │ │ │ │ +-record(abort_client,{message}).
            │ │ │ │  %%% Messages are: user_exists_at_other_node,
            │ │ │ │  %%%               you_are_not_logged_on
            │ │ │ │ --record(server_reply,{message}).
            │ │ │ │ +-record(server_reply,{message}).
            │ │ │ │  %%% Messages are: logged_on
            │ │ │ │  %%%               receiver_not_found
            │ │ │ │  %%%               sent  (Message has been sent (no guarantee)
            │ │ │ │  %%% Messages from Server to Client received in client/1 function
            │ │ │ │ --record(message_from,{from_name, message}).
            │ │ │ │ +-record(message_from,{from_name, message}).
            │ │ │ │  
            │ │ │ │  %%% Messages from shell to Client received in client/1 function
            │ │ │ │  %%% spawn(mess_client, client, [server_node(), Name])
            │ │ │ │ --record(message_to,{to_name, message}).
            │ │ │ │ +-record(message_to,{to_name, message}).
            │ │ │ │  %%% logoff
            │ │ │ │  
            │ │ │ │  %%%----END FILE----
            %%%----FILE user_interface.erl----
            │ │ │ │  
            │ │ │ │  %%% User interface to the messenger program
            │ │ │ │  %%% login(Name)
            │ │ │ │  %%%     One user at a time can log in from each Erlang node in the
            │ │ │ │ @@ -75,177 +75,177 @@
            │ │ │ │  %%%     Logs off anybody at that node
            │ │ │ │  
            │ │ │ │  %%% message(ToName, Message)
            │ │ │ │  %%%     sends Message to ToName. Error messages if the user of this
            │ │ │ │  %%%     function is not logged on or if ToName is not logged on at
            │ │ │ │  %%%     any node.
            │ │ │ │  
            │ │ │ │ --module(user_interface).
            │ │ │ │ --export([logon/1, logoff/0, message/2]).
            │ │ │ │ --include("mess_interface.hrl").
            │ │ │ │ --include("mess_config.hrl").
            │ │ │ │ +-module(user_interface).
            │ │ │ │ +-export([logon/1, logoff/0, message/2]).
            │ │ │ │ +-include("mess_interface.hrl").
            │ │ │ │ +-include("mess_config.hrl").
            │ │ │ │  
            │ │ │ │ -logon(Name) ->
            │ │ │ │ -    case whereis(mess_client) of
            │ │ │ │ +logon(Name) ->
            │ │ │ │ +    case whereis(mess_client) of
            │ │ │ │          undefined ->
            │ │ │ │ -            register(mess_client,
            │ │ │ │ -                     spawn(mess_client, client, [?server_node, Name]));
            │ │ │ │ +            register(mess_client,
            │ │ │ │ +                     spawn(mess_client, client, [?server_node, Name]));
            │ │ │ │          _ -> already_logged_on
            │ │ │ │      end.
            │ │ │ │  
            │ │ │ │ -logoff() ->
            │ │ │ │ +logoff() ->
            │ │ │ │      mess_client ! logoff.
            │ │ │ │  
            │ │ │ │ -message(ToName, Message) ->
            │ │ │ │ -    case whereis(mess_client) of % Test if the client is running
            │ │ │ │ +message(ToName, Message) ->
            │ │ │ │ +    case whereis(mess_client) of % Test if the client is running
            │ │ │ │          undefined ->
            │ │ │ │              not_logged_on;
            │ │ │ │ -        _ -> mess_client ! #message_to{to_name=ToName, message=Message},
            │ │ │ │ +        _ -> mess_client ! #message_to{to_name=ToName, message=Message},
            │ │ │ │               ok
            │ │ │ │  end.
            │ │ │ │  
            │ │ │ │  %%%----END FILE----
            %%%----FILE mess_client.erl----
            │ │ │ │  
            │ │ │ │  %%% The client process which runs on each user node
            │ │ │ │  
            │ │ │ │ --module(mess_client).
            │ │ │ │ --export([client/2]).
            │ │ │ │ --include("mess_interface.hrl").
            │ │ │ │ -
            │ │ │ │ -client(Server_Node, Name) ->
            │ │ │ │ -    {messenger, Server_Node} ! #logon{client_pid=self(), username=Name},
            │ │ │ │ -    await_result(),
            │ │ │ │ -    client(Server_Node).
            │ │ │ │ +-module(mess_client).
            │ │ │ │ +-export([client/2]).
            │ │ │ │ +-include("mess_interface.hrl").
            │ │ │ │ +
            │ │ │ │ +client(Server_Node, Name) ->
            │ │ │ │ +    {messenger, Server_Node} ! #logon{client_pid=self(), username=Name},
            │ │ │ │ +    await_result(),
            │ │ │ │ +    client(Server_Node).
            │ │ │ │  
            │ │ │ │ -client(Server_Node) ->
            │ │ │ │ +client(Server_Node) ->
            │ │ │ │      receive
            │ │ │ │          logoff ->
            │ │ │ │ -            exit(normal);
            │ │ │ │ -        #message_to{to_name=ToName, message=Message} ->
            │ │ │ │ -            {messenger, Server_Node} !
            │ │ │ │ -                #message{client_pid=self(), to_name=ToName, message=Message},
            │ │ │ │ -            await_result();
            │ │ │ │ -        {message_from, FromName, Message} ->
            │ │ │ │ -            io:format("Message from ~p: ~p~n", [FromName, Message])
            │ │ │ │ +            exit(normal);
            │ │ │ │ +        #message_to{to_name=ToName, message=Message} ->
            │ │ │ │ +            {messenger, Server_Node} !
            │ │ │ │ +                #message{client_pid=self(), to_name=ToName, message=Message},
            │ │ │ │ +            await_result();
            │ │ │ │ +        {message_from, FromName, Message} ->
            │ │ │ │ +            io:format("Message from ~p: ~p~n", [FromName, Message])
            │ │ │ │      end,
            │ │ │ │ -    client(Server_Node).
            │ │ │ │ +    client(Server_Node).
            │ │ │ │  
            │ │ │ │  %%% wait for a response from the server
            │ │ │ │ -await_result() ->
            │ │ │ │ +await_result() ->
            │ │ │ │      receive
            │ │ │ │ -        #abort_client{message=Why} ->
            │ │ │ │ -            io:format("~p~n", [Why]),
            │ │ │ │ -            exit(normal);
            │ │ │ │ -        #server_reply{message=What} ->
            │ │ │ │ -            io:format("~p~n", [What])
            │ │ │ │ +        #abort_client{message=Why} ->
            │ │ │ │ +            io:format("~p~n", [Why]),
            │ │ │ │ +            exit(normal);
            │ │ │ │ +        #server_reply{message=What} ->
            │ │ │ │ +            io:format("~p~n", [What])
            │ │ │ │      after 5000 ->
            │ │ │ │ -            io:format("No response from server~n", []),
            │ │ │ │ -            exit(timeout)
            │ │ │ │ +            io:format("No response from server~n", []),
            │ │ │ │ +            exit(timeout)
            │ │ │ │      end.
            │ │ │ │  
            │ │ │ │  %%%----END FILE---
            %%%----FILE mess_server.erl----
            │ │ │ │  
            │ │ │ │  %%% This is the server process of the messenger service
            │ │ │ │  
            │ │ │ │ --module(mess_server).
            │ │ │ │ --export([start_server/0, server/0]).
            │ │ │ │ --include("mess_interface.hrl").
            │ │ │ │ -
            │ │ │ │ -server() ->
            │ │ │ │ -    process_flag(trap_exit, true),
            │ │ │ │ -    server([]).
            │ │ │ │ +-module(mess_server).
            │ │ │ │ +-export([start_server/0, server/0]).
            │ │ │ │ +-include("mess_interface.hrl").
            │ │ │ │ +
            │ │ │ │ +server() ->
            │ │ │ │ +    process_flag(trap_exit, true),
            │ │ │ │ +    server([]).
            │ │ │ │  
            │ │ │ │  %%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
            │ │ │ │ -server(User_List) ->
            │ │ │ │ -    io:format("User list = ~p~n", [User_List]),
            │ │ │ │ +server(User_List) ->
            │ │ │ │ +    io:format("User list = ~p~n", [User_List]),
            │ │ │ │      receive
            │ │ │ │ -        #logon{client_pid=From, username=Name} ->
            │ │ │ │ -            New_User_List = server_logon(From, Name, User_List),
            │ │ │ │ -            server(New_User_List);
            │ │ │ │ -        {'EXIT', From, _} ->
            │ │ │ │ -            New_User_List = server_logoff(From, User_List),
            │ │ │ │ -            server(New_User_List);
            │ │ │ │ -        #message{client_pid=From, to_name=To, message=Message} ->
            │ │ │ │ -            server_transfer(From, To, Message, User_List),
            │ │ │ │ -            server(User_List)
            │ │ │ │ +        #logon{client_pid=From, username=Name} ->
            │ │ │ │ +            New_User_List = server_logon(From, Name, User_List),
            │ │ │ │ +            server(New_User_List);
            │ │ │ │ +        {'EXIT', From, _} ->
            │ │ │ │ +            New_User_List = server_logoff(From, User_List),
            │ │ │ │ +            server(New_User_List);
            │ │ │ │ +        #message{client_pid=From, to_name=To, message=Message} ->
            │ │ │ │ +            server_transfer(From, To, Message, User_List),
            │ │ │ │ +            server(User_List)
            │ │ │ │      end.
            │ │ │ │  
            │ │ │ │  %%% Start the server
            │ │ │ │ -start_server() ->
            │ │ │ │ -    register(messenger, spawn(?MODULE, server, [])).
            │ │ │ │ +start_server() ->
            │ │ │ │ +    register(messenger, spawn(?MODULE, server, [])).
            │ │ │ │  
            │ │ │ │  %%% Server adds a new user to the user list
            │ │ │ │ -server_logon(From, Name, User_List) ->
            │ │ │ │ +server_logon(From, Name, User_List) ->
            │ │ │ │      %% check if logged on anywhere else
            │ │ │ │ -    case lists:keymember(Name, 2, User_List) of
            │ │ │ │ +    case lists:keymember(Name, 2, User_List) of
            │ │ │ │          true ->
            │ │ │ │ -            From ! #abort_client{message=user_exists_at_other_node},
            │ │ │ │ +            From ! #abort_client{message=user_exists_at_other_node},
            │ │ │ │              User_List;
            │ │ │ │          false ->
            │ │ │ │ -            From ! #server_reply{message=logged_on},
            │ │ │ │ -            link(From),
            │ │ │ │ -            [{From, Name} | User_List]        %add user to the list
            │ │ │ │ +            From ! #server_reply{message=logged_on},
            │ │ │ │ +            link(From),
            │ │ │ │ +            [{From, Name} | User_List]        %add user to the list
            │ │ │ │      end.
            │ │ │ │  
            │ │ │ │  %%% Server deletes a user from the user list
            │ │ │ │ -server_logoff(From, User_List) ->
            │ │ │ │ -    lists:keydelete(From, 1, User_List).
            │ │ │ │ +server_logoff(From, User_List) ->
            │ │ │ │ +    lists:keydelete(From, 1, User_List).
            │ │ │ │  
            │ │ │ │  %%% Server transfers a message between user
            │ │ │ │ -server_transfer(From, To, Message, User_List) ->
            │ │ │ │ +server_transfer(From, To, Message, User_List) ->
            │ │ │ │      %% check that the user is logged on and who he is
            │ │ │ │ -    case lists:keysearch(From, 1, User_List) of
            │ │ │ │ +    case lists:keysearch(From, 1, User_List) of
            │ │ │ │          false ->
            │ │ │ │ -            From ! #abort_client{message=you_are_not_logged_on};
            │ │ │ │ -        {value, {_, Name}} ->
            │ │ │ │ -            server_transfer(From, Name, To, Message, User_List)
            │ │ │ │ +            From ! #abort_client{message=you_are_not_logged_on};
            │ │ │ │ +        {value, {_, Name}} ->
            │ │ │ │ +            server_transfer(From, Name, To, Message, User_List)
            │ │ │ │      end.
            │ │ │ │  %%% If the user exists, send the message
            │ │ │ │ -server_transfer(From, Name, To, Message, User_List) ->
            │ │ │ │ +server_transfer(From, Name, To, Message, User_List) ->
            │ │ │ │      %% Find the receiver and send the message
            │ │ │ │ -    case lists:keysearch(To, 2, User_List) of
            │ │ │ │ +    case lists:keysearch(To, 2, User_List) of
            │ │ │ │          false ->
            │ │ │ │ -            From ! #server_reply{message=receiver_not_found};
            │ │ │ │ -        {value, {ToPid, To}} ->
            │ │ │ │ -            ToPid ! #message_from{from_name=Name, message=Message},
            │ │ │ │ -            From !  #server_reply{message=sent}
            │ │ │ │ +            From ! #server_reply{message=receiver_not_found};
            │ │ │ │ +        {value, {ToPid, To}} ->
            │ │ │ │ +            ToPid ! #message_from{from_name=Name, message=Message},
            │ │ │ │ +            From !  #server_reply{message=sent}
            │ │ │ │      end.
            │ │ │ │  
            │ │ │ │  %%%----END FILE---

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Header Files │ │ │ │

            │ │ │ │

            As shown above, some files have extension .hrl. These are header files that │ │ │ │ -are included in the .erl files by:

            -include("File_Name").

            for example:

            -include("mess_interface.hrl").

            In the case above the file is fetched from the same directory as all the other │ │ │ │ +are included in the .erl files by:

            -include("File_Name").

            for example:

            -include("mess_interface.hrl").

            In the case above the file is fetched from the same directory as all the other │ │ │ │ files in the messenger example. (manual).

            .hrl files can contain any valid Erlang code but are most often used for record │ │ │ │ and macro definitions.

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Records │ │ │ │

            │ │ │ │ -

            A record is defined as:

            -record(name_of_record,{field_name1, field_name2, field_name3, ......}).

            For example:

            -record(message_to,{to_name, message}).

            This is equivalent to:

            {message_to, To_Name, Message}

            Creating a record is best illustrated by an example:

            #message_to{message="hello", to_name=fred)

            This creates:

            {message_to, fred, "hello"}

            Notice that you do not have to worry about the order you assign values to the │ │ │ │ +

            A record is defined as:

            -record(name_of_record,{field_name1, field_name2, field_name3, ......}).

            For example:

            -record(message_to,{to_name, message}).

            This is equivalent to:

            {message_to, To_Name, Message}

            Creating a record is best illustrated by an example:

            #message_to{message="hello", to_name=fred)

            This creates:

            {message_to, fred, "hello"}

            Notice that you do not have to worry about the order you assign values to the │ │ │ │ various parts of the records when you create it. The advantage of using records │ │ │ │ is that by placing their definitions in header files you can conveniently define │ │ │ │ interfaces that are easy to change. For example, if you want to add a new field │ │ │ │ to the record, you only have to change the code where the new field is used and │ │ │ │ not at every place the record is referred to. If you leave out a field when │ │ │ │ creating a record, it gets the value of the atom undefined. (manual)

            Pattern matching with records is very similar to creating records. For example, │ │ │ │ -inside a case or receive:

            #message_to{to_name=ToName, message=Message} ->

            This is the same as:

            {message_to, ToName, Message}

            │ │ │ │ +inside a case or receive:

            #message_to{to_name=ToName, message=Message} ->

            This is the same as:

            {message_to, ToName, Message}

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Macros │ │ │ │

            │ │ │ │

            Another thing that has been added to the messenger is a macro. The file │ │ │ │ mess_config.hrl contains the definition:

            %%% Configure the location of the server node,
            │ │ │ │ --define(server_node, messenger@super).

            This file is included in mess_server.erl:

            -include("mess_config.hrl").

            Every occurrence of ?server_node in mess_server.erl is now replaced by │ │ │ │ -messenger@super.

            A macro is also used when spawning the server process:

            spawn(?MODULE, server, [])

            This is a standard macro (that is, defined by the system, not by the user). │ │ │ │ +-define(server_node, messenger@super).

            This file is included in mess_server.erl:

            -include("mess_config.hrl").

            Every occurrence of ?server_node in mess_server.erl is now replaced by │ │ │ │ +messenger@super.

            A macro is also used when spawning the server process:

            spawn(?MODULE, server, [])

            This is a standard macro (that is, defined by the system, not by the user). │ │ │ │ ?MODULE is always replaced by the name of the current module (that is, the │ │ │ │ -module definition near the start of the file). There are more advanced ways │ │ │ │ of using macros with, for example, parameters.

            The three Erlang (.erl) files in the messenger example are individually │ │ │ │ compiled into object code file (.beam). The Erlang system loads and links │ │ │ │ these files into the system when they are referred to during execution of the │ │ │ │ code. In this case, they are simply put in our current working directory (that │ │ │ │ is, the place you have done "cd" to). There are ways of putting the .beam │ │ │ ├── OEBPS/prog_ex_records.xhtml │ │ │ │ @@ -27,105 +27,105 @@ │ │ │ │ Records and Tuples │ │ │ │ │ │ │ │

            The main advantage of using records rather than tuples is that fields in a │ │ │ │ record are accessed by name, whereas fields in a tuple are accessed by position. │ │ │ │ To illustrate these differences, suppose that you want to represent a person │ │ │ │ with the tuple {Name, Address, Phone}.

            To write functions that manipulate this data, remember the following:

            • The Name field is the first element of the tuple.
            • The Address field is the second element.
            • The Phone field is the third element.

            For example, to extract data from a variable P that contains such a tuple, you │ │ │ │ can write the following code and then use pattern matching to extract the │ │ │ │ -relevant fields:

            Name = element(1, P),
            │ │ │ │ -Address = element(2, P),
            │ │ │ │ +relevant fields:

            Name = element(1, P),
            │ │ │ │ +Address = element(2, P),
            │ │ │ │  ...

            Such code is difficult to read and understand, and errors occur if the numbering │ │ │ │ of the elements in the tuple is wrong. If the data representation of the fields │ │ │ │ is changed, by re-ordering, adding, or removing fields, all references to the │ │ │ │ person tuple must be checked and possibly modified.

            Records allow references to the fields by name, instead of by position. In the │ │ │ │ -following example, a record instead of a tuple is used to store the data:

            -record(person, {name, phone, address}).

            This enables references to the fields of the record by name. For example, if P │ │ │ │ +following example, a record instead of a tuple is used to store the data:

            -record(person, {name, phone, address}).

            This enables references to the fields of the record by name. For example, if P │ │ │ │ is a variable whose value is a person record, the following code access the │ │ │ │ name and address fields of the records:

            Name = P#person.name,
            │ │ │ │  Address = P#person.address,
            │ │ │ │ -...

            Internally, records are represented using tagged tuples:

            {person, Name, Phone, Address}

            │ │ │ │ +...

            Internally, records are represented using tagged tuples:

            {person, Name, Phone, Address}

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Defining a Record │ │ │ │

            │ │ │ │

            This following definition of a person is used in several examples in this │ │ │ │ section. Three fields are included, name, phone, and address. The default │ │ │ │ values for name and phone is "" and [], respectively. The default value for │ │ │ │ address is the atom undefined, since no default value is supplied for this │ │ │ │ -field:

            -record(person, {name = "", phone = [], address}).

            The record must be defined in the shell to enable use of the record syntax in │ │ │ │ -the examples:

            > rd(person, {name = "", phone = [], address}).
            │ │ │ │ +field:

            -record(person, {name = "", phone = [], address}).

            The record must be defined in the shell to enable use of the record syntax in │ │ │ │ +the examples:

            > rd(person, {name = "", phone = [], address}).
            │ │ │ │  person

            This is because record definitions are only available at compile time, not at │ │ │ │ runtime. For details on records in the shell, see the shell manual page in │ │ │ │ STDLIB.

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Creating a Record │ │ │ │

            │ │ │ │ -

            A new person record is created as follows:

            > #person{phone=[0,8,2,3,4,3,1,2], name="Robert"}.
            │ │ │ │ -#person{name = "Robert",phone = [0,8,2,3,4,3,1,2],address = undefined}

            As the address field was omitted, its default value is used.

            From Erlang 5.1/OTP R8B, a value to all fields in a record can be set with the │ │ │ │ -special field _. _ means "all fields not explicitly specified".

            Example:

            > #person{name = "Jakob", _ = '_'}.
            │ │ │ │ -#person{name = "Jakob",phone = '_',address = '_'}

            It is primarily intended to be used in ets:match/2 and │ │ │ │ +

            A new person record is created as follows:

            > #person{phone=[0,8,2,3,4,3,1,2], name="Robert"}.
            │ │ │ │ +#person{name = "Robert",phone = [0,8,2,3,4,3,1,2],address = undefined}

            As the address field was omitted, its default value is used.

            From Erlang 5.1/OTP R8B, a value to all fields in a record can be set with the │ │ │ │ +special field _. _ means "all fields not explicitly specified".

            Example:

            > #person{name = "Jakob", _ = '_'}.
            │ │ │ │ +#person{name = "Jakob",phone = '_',address = '_'}

            It is primarily intended to be used in ets:match/2 and │ │ │ │ mnesia:match_object/3, to set record fields to the atom '_'. (This is a │ │ │ │ wildcard in ets:match/2.)

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Accessing a Record Field │ │ │ │

            │ │ │ │ -

            The following example shows how to access a record field:

            > P = #person{name = "Joe", phone = [0,8,2,3,4,3,1,2]}.
            │ │ │ │ -#person{name = "Joe",phone = [0,8,2,3,4,3,1,2],address = undefined}
            │ │ │ │ +

            The following example shows how to access a record field:

            > P = #person{name = "Joe", phone = [0,8,2,3,4,3,1,2]}.
            │ │ │ │ +#person{name = "Joe",phone = [0,8,2,3,4,3,1,2],address = undefined}
            │ │ │ │  > P#person.name.
            │ │ │ │  "Joe"

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Updating a Record │ │ │ │

            │ │ │ │ -

            The following example shows how to update a record:

            > P1 = #person{name="Joe", phone=[1,2,3], address="A street"}.
            │ │ │ │ -#person{name = "Joe",phone = [1,2,3],address = "A street"}
            │ │ │ │ -> P2 = P1#person{name="Robert"}.
            │ │ │ │ -#person{name = "Robert",phone = [1,2,3],address = "A street"}

            │ │ │ │ +

            The following example shows how to update a record:

            > P1 = #person{name="Joe", phone=[1,2,3], address="A street"}.
            │ │ │ │ +#person{name = "Joe",phone = [1,2,3],address = "A street"}
            │ │ │ │ +> P2 = P1#person{name="Robert"}.
            │ │ │ │ +#person{name = "Robert",phone = [1,2,3],address = "A street"}

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Type Testing │ │ │ │

            │ │ │ │

            The following example shows that the guard succeeds if P is record of type │ │ │ │ -person:

            foo(P) when is_record(P, person) -> a_person;
            │ │ │ │ -foo(_) -> not_a_person.

            │ │ │ │ +person:

            foo(P) when is_record(P, person) -> a_person;
            │ │ │ │ +foo(_) -> not_a_person.

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Pattern Matching │ │ │ │

            │ │ │ │

            Matching can be used in combination with records, as shown in the following │ │ │ │ -example:

            > P3 = #person{name="Joe", phone=[0,0,7], address="A street"}.
            │ │ │ │ -#person{name = "Joe",phone = [0,0,7],address = "A street"}
            │ │ │ │ -> #person{name = Name} = P3, Name.
            │ │ │ │ +example:

            > P3 = #person{name="Joe", phone=[0,0,7], address="A street"}.
            │ │ │ │ +#person{name = "Joe",phone = [0,0,7],address = "A street"}
            │ │ │ │ +> #person{name = Name} = P3, Name.
            │ │ │ │  "Joe"

            The following function takes a list of person records and searches for the │ │ │ │ -phone number of a person with a particular name:

            find_phone([#person{name=Name, phone=Phone} | _], Name) ->
            │ │ │ │ -    {found,  Phone};
            │ │ │ │ -find_phone([_| T], Name) ->
            │ │ │ │ -    find_phone(T, Name);
            │ │ │ │ -find_phone([], Name) ->
            │ │ │ │ +phone number of a person with a particular name:

            find_phone([#person{name=Name, phone=Phone} | _], Name) ->
            │ │ │ │ +    {found,  Phone};
            │ │ │ │ +find_phone([_| T], Name) ->
            │ │ │ │ +    find_phone(T, Name);
            │ │ │ │ +find_phone([], Name) ->
            │ │ │ │      not_found.

            The fields referred to in the pattern can be given in any order.

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Nested Records │ │ │ │

            │ │ │ │

            The value of a field in a record can be an instance of a record. Retrieval of │ │ │ │ nested data can be done stepwise, or in a single step, as shown in the following │ │ │ │ -example:

            -record(name, {first = "Robert", last = "Ericsson"}).
            │ │ │ │ --record(person, {name = #name{}, phone}).
            │ │ │ │ +example:

            -record(name, {first = "Robert", last = "Ericsson"}).
            │ │ │ │ +-record(person, {name = #name{}, phone}).
            │ │ │ │  
            │ │ │ │ -demo() ->
            │ │ │ │ -  P = #person{name= #name{first="Robert",last="Virding"}, phone=123},
            │ │ │ │ -  First = (P#person.name)#name.first.

            Here, demo() evaluates to "Robert".

            │ │ │ │ +demo() -> │ │ │ │ + P = #person{name= #name{first="Robert",last="Virding"}, phone=123}, │ │ │ │ + First = (P#person.name)#name.first.

            Here, demo() evaluates to "Robert".

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ A Longer Example │ │ │ │

            │ │ │ │

            Comments are embedded in the following example:

            %% File: person.hrl
            │ │ │ │  
            │ │ │ │ @@ -135,44 +135,44 @@
            │ │ │ │  %%    name:  A string (default is undefined).
            │ │ │ │  %%    age:   An integer (default is undefined).
            │ │ │ │  %%    phone: A list of integers (default is []).
            │ │ │ │  %%    dict:  A dictionary containing various information
            │ │ │ │  %%           about the person.
            │ │ │ │  %%           A {Key, Value} list (default is the empty list).
            │ │ │ │  %%------------------------------------------------------------
            │ │ │ │ --record(person, {name, age, phone = [], dict = []}).
            -module(person).
            │ │ │ │ --include("person.hrl").
            │ │ │ │ --compile(export_all). % For test purposes only.
            │ │ │ │ +-record(person, {name, age, phone = [], dict = []}).
            -module(person).
            │ │ │ │ +-include("person.hrl").
            │ │ │ │ +-compile(export_all). % For test purposes only.
            │ │ │ │  
            │ │ │ │  %% This creates an instance of a person.
            │ │ │ │  %%   Note: The phone number is not supplied so the
            │ │ │ │  %%         default value [] will be used.
            │ │ │ │  
            │ │ │ │ -make_hacker_without_phone(Name, Age) ->
            │ │ │ │ -   #person{name = Name, age = Age,
            │ │ │ │ -           dict = [{computer_knowledge, excellent},
            │ │ │ │ -                   {drinks, coke}]}.
            │ │ │ │ +make_hacker_without_phone(Name, Age) ->
            │ │ │ │ +   #person{name = Name, age = Age,
            │ │ │ │ +           dict = [{computer_knowledge, excellent},
            │ │ │ │ +                   {drinks, coke}]}.
            │ │ │ │  
            │ │ │ │  %% This demonstrates matching in arguments
            │ │ │ │  
            │ │ │ │ -print(#person{name = Name, age = Age,
            │ │ │ │ -              phone = Phone, dict = Dict}) ->
            │ │ │ │ -  io:format("Name: ~s, Age: ~w, Phone: ~w ~n"
            │ │ │ │ -            "Dictionary: ~w.~n", [Name, Age, Phone, Dict]).
            │ │ │ │ +print(#person{name = Name, age = Age,
            │ │ │ │ +              phone = Phone, dict = Dict}) ->
            │ │ │ │ +  io:format("Name: ~s, Age: ~w, Phone: ~w ~n"
            │ │ │ │ +            "Dictionary: ~w.~n", [Name, Age, Phone, Dict]).
            │ │ │ │  
            │ │ │ │  %% Demonstrates type testing, selector, updating.
            │ │ │ │  
            │ │ │ │ -birthday(P) when is_record(P, person) ->
            │ │ │ │ -   P#person{age = P#person.age + 1}.
            │ │ │ │ +birthday(P) when is_record(P, person) ->
            │ │ │ │ +   P#person{age = P#person.age + 1}.
            │ │ │ │  
            │ │ │ │ -register_two_hackers() ->
            │ │ │ │ -   Hacker1 = make_hacker_without_phone("Joe", 29),
            │ │ │ │ -   OldHacker = birthday(Hacker1),
            │ │ │ │ +register_two_hackers() ->
            │ │ │ │ +   Hacker1 = make_hacker_without_phone("Joe", 29),
            │ │ │ │ +   OldHacker = birthday(Hacker1),
            │ │ │ │     % The central_register_server should have
            │ │ │ │     % an interface function for this.
            │ │ │ │ -   central_register_server ! {register_person, Hacker1},
            │ │ │ │ -   central_register_server ! {register_person,
            │ │ │ │ -             OldHacker#person{name = "Robert",
            │ │ │ │ -                              phone = [0,8,3,2,4,5,3,1]}}.
            │ │ │ │ +
            central_register_server ! {register_person, Hacker1}, │ │ │ │ + central_register_server ! {register_person, │ │ │ │ + OldHacker#person{name = "Robert", │ │ │ │ + phone = [0,8,3,2,4,5,3,1]}}.
            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/patterns.xhtml │ │ │ │ @@ -33,16 +33,16 @@ │ │ │ │ succeeds, any unbound variables in the pattern become bound. If the matching │ │ │ │ fails, an exception is raised.

            Examples:

            1> X.
            │ │ │ │  ** 1:1: variable 'X' is unbound **
            │ │ │ │  2> X = 2.
            │ │ │ │  2
            │ │ │ │  3> X + 1.
            │ │ │ │  3
            │ │ │ │ -4> {X, Y} = {1, 2}.
            │ │ │ │ +4> {X, Y} = {1, 2}.
            │ │ │ │  ** exception error: no match of right hand side value {1,2}
            │ │ │ │ -5> {X, Y} = {2, 3}.
            │ │ │ │ -{2,3}
            │ │ │ │ +5> {X, Y} = {2, 3}.
            │ │ │ │ +{2,3}
            │ │ │ │  6> Y.
            │ │ │ │  3
            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/otp-patch-apply.xhtml │ │ │ │ @@ -106,13 +106,13 @@ │ │ │ │ │ │ │ │ Sanity check │ │ │ │ │ │ │ │

            The application dependencies can be checked using the Erlang shell. │ │ │ │ Application dependencies are verified among installed applications by │ │ │ │ otp_patch_apply, but these are not necessarily those actually loaded. │ │ │ │ By calling system_information:sanity_check() one can validate │ │ │ │ -dependencies among applications actually loaded.

            1> system_information:sanity_check().
            │ │ │ │ +dependencies among applications actually loaded.

            1> system_information:sanity_check().
            │ │ │ │  ok

            Please take a look at the reference of sanity_check() for more │ │ │ │ information.

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/opaques.xhtml │ │ │ │ @@ -27,24 +27,24 @@ │ │ │ │ Opaque Type Aliases │ │ │ │ │ │ │ │

            The main use case for opacity in Erlang is to hide the implementation of a data │ │ │ │ type, enabling evolving the API while minimizing the risk of breaking consumers. │ │ │ │ The runtime does not check opacity. Dialyzer provides some opacity-checking, but │ │ │ │ the rest is up to convention.

            This document explains what Erlang opacity is (and the trade-offs involved) via │ │ │ │ the example of the sets:set() data type. This type was │ │ │ │ -defined in the sets module like this:

            -opaque set(Element) :: #set{segs :: segs(Element)}.

            OTP 24 changed the definition to the following in │ │ │ │ -this commit.

            -opaque set(Element) :: #set{segs :: segs(Element)} | #{Element => ?VALUE}.

            And this change was safer and more backwards-compatible than if the type had │ │ │ │ +defined in the sets module like this:

            -opaque set(Element) :: #set{segs :: segs(Element)}.

            OTP 24 changed the definition to the following in │ │ │ │ +this commit.

            -opaque set(Element) :: #set{segs :: segs(Element)} | #{Element => ?VALUE}.

            And this change was safer and more backwards-compatible than if the type had │ │ │ │ been defined with -type instead of -opaque. Here is why: when a module │ │ │ │ defines an -opaque, the contract is that only the defining module should rely │ │ │ │ on the definition of the type: no other modules should rely on the definition.

            This means that code that pattern-matched on set as a record/tuple technically │ │ │ │ broke the contract, and opted in to being potentially broken when the definition │ │ │ │ of set() changed. Before OTP 24, this code printed ok. In OTP 24 it may │ │ │ │ -error:

            case sets:new() of
            │ │ │ │ -    Set when is_tuple(Set) ->
            │ │ │ │ -        io:format("ok")
            │ │ │ │ +error:

            case sets:new() of
            │ │ │ │ +    Set when is_tuple(Set) ->
            │ │ │ │ +        io:format("ok")
            │ │ │ │  end.

            When working with an opaque defined in another module, here are some │ │ │ │ recommendations:

            • Don't examine the underlying type using pattern-matching, guards, or functions │ │ │ │ that reveal the type, such as tuple_size/1 .
            • Instead, use functions provided by the module for working with the type. For │ │ │ │ example, sets module provides sets:new/0, sets:add_element/2, │ │ │ │ sets:is_element/2, and so on.
            • sets:set(a) is a subtype of sets:set(a | b) and not the │ │ │ │ other way around. Generally, you can rely on the property that the_opaque(T) │ │ │ │ is a subtype of the_opaque(U) when T is a subtype of U.

            When defining your own opaques, here are some recommendations:

            • Since consumers are expected to not rely on the definition of the opaque type, │ │ │ ├── OEBPS/nif.xhtml │ │ │ │ @@ -38,26 +38,26 @@ │ │ │ │ Erlang Program │ │ │ │ │ │ │ │

              Even if all functions of a module are NIFs, an Erlang module is still needed for │ │ │ │ two reasons:

              • The NIF library must be explicitly loaded by Erlang code in the same module.
              • All NIFs of a module must have an Erlang implementation as well.

              Normally these are minimal stub implementations that throw an exception. But │ │ │ │ they can also be used as fallback implementations for functions that do not have │ │ │ │ native implementations on some architectures.

              NIF libraries are loaded by calling erlang:load_nif/2, with the name of the │ │ │ │ shared library as argument. The second argument can be any term that will be │ │ │ │ -passed on to the library and used for initialization:

              -module(complex6).
              │ │ │ │ --export([foo/1, bar/1]).
              │ │ │ │ --nifs([foo/1, bar/1]).
              │ │ │ │ --on_load(init/0).
              │ │ │ │ -
              │ │ │ │ -init() ->
              │ │ │ │ -    ok = erlang:load_nif("./complex6_nif", 0).
              │ │ │ │ -
              │ │ │ │ -foo(_X) ->
              │ │ │ │ -    erlang:nif_error(nif_library_not_loaded).
              │ │ │ │ -bar(_Y) ->
              │ │ │ │ -    erlang:nif_error(nif_library_not_loaded).

              Here, the directive on_load is used to get function init to be automatically │ │ │ │ +passed on to the library and used for initialization:

              -module(complex6).
              │ │ │ │ +-export([foo/1, bar/1]).
              │ │ │ │ +-nifs([foo/1, bar/1]).
              │ │ │ │ +-on_load(init/0).
              │ │ │ │ +
              │ │ │ │ +init() ->
              │ │ │ │ +    ok = erlang:load_nif("./complex6_nif", 0).
              │ │ │ │ +
              │ │ │ │ +foo(_X) ->
              │ │ │ │ +    erlang:nif_error(nif_library_not_loaded).
              │ │ │ │ +bar(_Y) ->
              │ │ │ │ +    erlang:nif_error(nif_library_not_loaded).

              Here, the directive on_load is used to get function init to be automatically │ │ │ │ called when the module is loaded. If init returns anything other than ok, │ │ │ │ such when the loading of the NIF library fails in this example, the module is │ │ │ │ unloaded and calls to functions within it, fail.

              Loading the NIF library overrides the stub implementations and cause calls to │ │ │ │ foo and bar to be dispatched to the NIF implementations instead.

              │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -70,43 +70,43 @@ │ │ │ │ is a shared module, not a program, no main function is to be present.

              The function arguments passed to a NIF appears in an array argv, with argc │ │ │ │ as the length of the array, and thus the arity of the function. The Nth argument │ │ │ │ of the function can be accessed as argv[N-1]. NIFs also take an environment │ │ │ │ argument that serves as an opaque handle that is needed to be passed on to most │ │ │ │ API functions. The environment contains information about the calling Erlang │ │ │ │ process:

              #include <erl_nif.h>
              │ │ │ │  
              │ │ │ │ -extern int foo(int x);
              │ │ │ │ -extern int bar(int y);
              │ │ │ │ +extern int foo(int x);
              │ │ │ │ +extern int bar(int y);
              │ │ │ │  
              │ │ │ │ -static ERL_NIF_TERM foo_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
              │ │ │ │ -{
              │ │ │ │ +static ERL_NIF_TERM foo_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
              │ │ │ │ +{
              │ │ │ │      int x, ret;
              │ │ │ │ -    if (!enif_get_int(env, argv[0], &x)) {
              │ │ │ │ -	return enif_make_badarg(env);
              │ │ │ │ -    }
              │ │ │ │ -    ret = foo(x);
              │ │ │ │ -    return enif_make_int(env, ret);
              │ │ │ │ -}
              │ │ │ │ +    if (!enif_get_int(env, argv[0], &x)) {
              │ │ │ │ +	return enif_make_badarg(env);
              │ │ │ │ +    }
              │ │ │ │ +    ret = foo(x);
              │ │ │ │ +    return enif_make_int(env, ret);
              │ │ │ │ +}
              │ │ │ │  
              │ │ │ │ -static ERL_NIF_TERM bar_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
              │ │ │ │ -{
              │ │ │ │ +static ERL_NIF_TERM bar_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
              │ │ │ │ +{
              │ │ │ │      int y, ret;
              │ │ │ │ -    if (!enif_get_int(env, argv[0], &y)) {
              │ │ │ │ -	return enif_make_badarg(env);
              │ │ │ │ -    }
              │ │ │ │ -    ret = bar(y);
              │ │ │ │ -    return enif_make_int(env, ret);
              │ │ │ │ -}
              │ │ │ │ -
              │ │ │ │ -static ErlNifFunc nif_funcs[] = {
              │ │ │ │ -    {"foo", 1, foo_nif},
              │ │ │ │ -    {"bar", 1, bar_nif}
              │ │ │ │ -};
              │ │ │ │ +    if (!enif_get_int(env, argv[0], &y)) {
              │ │ │ │ +	return enif_make_badarg(env);
              │ │ │ │ +    }
              │ │ │ │ +    ret = bar(y);
              │ │ │ │ +    return enif_make_int(env, ret);
              │ │ │ │ +}
              │ │ │ │ +
              │ │ │ │ +static ErlNifFunc nif_funcs[] = {
              │ │ │ │ +    {"foo", 1, foo_nif},
              │ │ │ │ +    {"bar", 1, bar_nif}
              │ │ │ │ +};
              │ │ │ │  
              │ │ │ │ -ERL_NIF_INIT(complex6, nif_funcs, NULL, NULL, NULL, NULL)

              Here, ERL_NIF_INIT has the following arguments:

              • The first argument must be the name of the Erlang module as a C-identifier. It │ │ │ │ +ERL_NIF_INIT(complex6, nif_funcs, NULL, NULL, NULL, NULL)

            Here, ERL_NIF_INIT has the following arguments:

            • The first argument must be the name of the Erlang module as a C-identifier. It │ │ │ │ will be stringified by the macro.
            • The second argument is the array of ErlNifFunc structures containing name, │ │ │ │ arity, and function pointer of each NIF.
            • The remaining arguments are pointers to callback functions that can be used to │ │ │ │ initialize the library. They are not used in this simple example, hence they │ │ │ │ are all set to NULL.

            Function arguments and return values are represented as values of type │ │ │ │ ERL_NIF_TERM. Here, functions like enif_get_int and enif_make_int are used │ │ │ │ to convert between Erlang term and C-type. If the function argument argv[0] is │ │ │ │ not an integer, enif_get_int returns false, in which case it returns by │ │ │ │ @@ -114,22 +114,22 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Running the Example │ │ │ │ │ │ │ │

            Step 1. Compile the C code:

            unix> gcc -o complex6_nif.so -fpic -shared complex.c complex6_nif.c
            │ │ │ │  windows> cl -LD -MD -Fe complex6_nif.dll complex.c complex6_nif.c

            Step 2: Start Erlang and compile the Erlang code:

            > erl
            │ │ │ │ -Erlang R13B04 (erts-5.7.5) [64-bit] [smp:4:4] [rq:4] [async-threads:0] [kernel-poll:false]
            │ │ │ │ +Erlang R13B04 (erts-5.7.5) [64-bit] [smp:4:4] [rq:4] [async-threads:0] [kernel-poll:false]
            │ │ │ │  
            │ │ │ │ -Eshell V5.7.5  (abort with ^G)
            │ │ │ │ -1> c(complex6).
            │ │ │ │ -{ok,complex6}

            Step 3: Run the example:

            3> complex6:foo(3).
            │ │ │ │ +Eshell V5.7.5  (abort with ^G)
            │ │ │ │ +1> c(complex6).
            │ │ │ │ +{ok,complex6}

            Step 3: Run the example:

            3> complex6:foo(3).
            │ │ │ │  4
            │ │ │ │ -4> complex6:bar(5).
            │ │ │ │ +4> complex6:bar(5).
            │ │ │ │  10
            │ │ │ │ -5> complex6:foo("not an integer").
            │ │ │ │ +5> complex6:foo("not an integer").
            │ │ │ │  ** exception error: bad argument
            │ │ │ │       in function  complex6:foo/1
            │ │ │ │          called as comlpex6:foo("not an integer")
            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/modules.xhtml │ │ │ │ @@ -23,20 +23,20 @@ │ │ │ │

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Module Syntax │ │ │ │

            │ │ │ │

            Erlang code is divided into modules. A module consists of a sequence of │ │ │ │ -attributes and function declarations, each terminated by a period (.).

            Example:

            -module(m).          % module attribute
            │ │ │ │ --export([fact/1]).   % module attribute
            │ │ │ │ +attributes and function declarations, each terminated by a period (.).

            Example:

            -module(m).          % module attribute
            │ │ │ │ +-export([fact/1]).   % module attribute
            │ │ │ │  
            │ │ │ │ -fact(N) when N>0 ->  % beginning of function declaration
            │ │ │ │ -    N * fact(N-1);   %  |
            │ │ │ │ -fact(0) ->           %  |
            │ │ │ │ +fact(N) when N>0 ->  % beginning of function declaration
            │ │ │ │ +    N * fact(N-1);   %  |
            │ │ │ │ +fact(0) ->           %  |
            │ │ │ │      1.               % end of function declaration

            For a description of function declarations, see │ │ │ │ Function Declaration Syntax.

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Module Attributes │ │ │ │

            │ │ │ │ @@ -81,71 +81,71 @@ │ │ │ │ meaning.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Behaviour Module Attribute │ │ │ │

          │ │ │ │

          It is possible to specify that the module is the callback module for a │ │ │ │ -behaviour:

          -behaviour(Behaviour).

          The atom Behaviour gives the name of the behaviour, which can be a │ │ │ │ +behaviour:

          -behaviour(Behaviour).

          The atom Behaviour gives the name of the behaviour, which can be a │ │ │ │ user-defined behaviour or one of the following OTP standard behaviours:

          • gen_server
          • gen_statem
          • gen_event
          • supervisor

          The spelling behavior is also accepted.

          The callback functions of the module can be specified either directly by the │ │ │ │ -exported function behaviour_info/1:

          behaviour_info(callbacks) -> Callbacks.

          or by a -callback attribute for each callback function:

          -callback Name(Arguments) -> Result.

          Here, Arguments is a list of zero or more arguments. The -callback attribute │ │ │ │ +exported function behaviour_info/1:

          behaviour_info(callbacks) -> Callbacks.

          or by a -callback attribute for each callback function:

          -callback Name(Arguments) -> Result.

          Here, Arguments is a list of zero or more arguments. The -callback attribute │ │ │ │ is to be preferred since the extra type information can be used by tools to │ │ │ │ produce documentation or find discrepancies.

          Read more about behaviours and callback modules in │ │ │ │ OTP Design Principles.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Record Definitions │ │ │ │

          │ │ │ │ -

          The same syntax as for module attributes is used for record definitions:

          -record(Record, Fields).

          Record definitions are allowed anywhere in a module, also among the function │ │ │ │ +

          The same syntax as for module attributes is used for record definitions:

          -record(Record, Fields).

          Record definitions are allowed anywhere in a module, also among the function │ │ │ │ declarations. Read more in Records.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Preprocessor │ │ │ │

          │ │ │ │

          The same syntax as for module attributes is used by the preprocessor, which │ │ │ │ -supports file inclusion, macros, and conditional compilation:

          -include("SomeFile.hrl").
          │ │ │ │ --define(Macro, Replacement).

          Read more in Preprocessor.

          │ │ │ │ +supports file inclusion, macros, and conditional compilation:

          -include("SomeFile.hrl").
          │ │ │ │ +-define(Macro, Replacement).

          Read more in Preprocessor.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Setting File and Line │ │ │ │

          │ │ │ │

          The same syntax as for module attributes is used for changing the pre-defined │ │ │ │ -macros ?FILE and ?LINE:

          -file(File, Line).

          This attribute is used by tools, such as Yecc, to inform the compiler that the │ │ │ │ +macros ?FILE and ?LINE:

          -file(File, Line).

          This attribute is used by tools, such as Yecc, to inform the compiler that the │ │ │ │ source program is generated by another tool. It also indicates the │ │ │ │ correspondence of source files to lines of the original user-written file, from │ │ │ │ which the source program is produced.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Types and function specifications │ │ │ │

          │ │ │ │

          A similar syntax as for module attributes is used for specifying types and │ │ │ │ -function specifications:

          -type my_type() :: atom() | integer().
          │ │ │ │ --spec my_function(integer()) -> integer().

          Read more in Types and Function specifications.

          The description is based on │ │ │ │ +function specifications:

          -type my_type() :: atom() | integer().
          │ │ │ │ +-spec my_function(integer()) -> integer().

          Read more in Types and Function specifications.

          The description is based on │ │ │ │ EEP8 - Types and function specifications, │ │ │ │ which is not to be further updated.

          │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Documentation attributes │ │ │ │

          │ │ │ │

          The module attribute -doc(Documentation) is used to provide user documentation │ │ │ │ -for a function/type/callback:

          -doc("Example documentation").
          │ │ │ │ -example() -> ok.

          The attribute should be placed just before the entity it documents.The │ │ │ │ +for a function/type/callback:

          -doc("Example documentation").
          │ │ │ │ +example() -> ok.

          The attribute should be placed just before the entity it documents.The │ │ │ │ parenthesis are optional around Documentation. The allowed values for │ │ │ │ Documentation are:

          • literal string or │ │ │ │ utf-8 encoded binary string - The string │ │ │ │ documenting the entity. Any literal string is allowed, so both │ │ │ │ triple quoted strings and │ │ │ │ sigils that translate to literal strings can be used. │ │ │ │ -The following examples are equivalent:

            -doc("Example \"docs\"").
            │ │ │ │ --doc(<<"Example \"docs\""/utf8>>).
            │ │ │ │ +The following examples are equivalent:

            -doc("Example \"docs\"").
            │ │ │ │ +-doc(<<"Example \"docs\""/utf8>>).
            │ │ │ │  -doc ~S/Example "docs"/.
            │ │ │ │  -doc """
            │ │ │ │     Example "docs"
            │ │ │ │     """
            │ │ │ │  -doc ~B|Example "docs"|.

            For clarity it is recommended to use either normal "strings" or triple │ │ │ │ quoted strings for documentation attributes.

          • {file, file:name/0 } - Read the contents of filename and use │ │ │ │ that as the documentation string.

          • false - Set the current entity as hidden, that is, it should not be │ │ │ │ @@ -158,15 +158,15 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ The feature directive │ │ │ │ │ │ │ │

            While not a module attribute, but rather a directive (since it might affect │ │ │ │ syntax), there is the -feature(..) directive used for enabling and disabling │ │ │ │ -features.

            The syntax is similar to that of an attribute, but has two arguments:

            -feature(FeatureName, enable | disable).

            Note that the feature directive can only appear │ │ │ │ +features.

            The syntax is similar to that of an attribute, but has two arguments:

            -feature(FeatureName, enable | disable).

            Note that the feature directive can only appear │ │ │ │ in a prefix of the module.

            │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Comments │ │ │ │

            │ │ │ │

            Comments can be placed anywhere in a module except within strings and │ │ │ ├── OEBPS/maps.xhtml │ │ │ │ @@ -53,16 +53,16 @@ │ │ │ │ single function that constructs the map using the map syntax and always use │ │ │ │ it.

          • Always update the map using the := operator (that is, requiring that an │ │ │ │ element with that key already exists). The := operator is slightly more │ │ │ │ efficient, and it helps catching mispellings of keys.

          • Whenever possible, match multiple map elements at once.

          • Whenever possible, update multiple map elements at once.

          • Avoid default values and the maps:get/3 function. If there are default │ │ │ │ values, sharing of keys between different instances of the map will be less │ │ │ │ effective, and it is not possible to match multiple elements having default │ │ │ │ values in one go.

          • To avoid having to deal with a map that may lack some keys, maps:merge/2 can │ │ │ │ -efficiently add multiple default values. For example:

            DefaultMap = #{shoe_size => 42, editor => emacs},
            │ │ │ │ -MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)

          │ │ │ │ +efficiently add multiple default values. For example:

          DefaultMap = #{shoe_size => 42, editor => emacs},
          │ │ │ │ +MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)

        │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Using Maps as Dictionaries │ │ │ │

        │ │ │ │

        Using a map as a dictionary implies the following usage pattern:

        • Keys are usually variables not known at compile-time.
        • There can be any number of elements in the map.
        • Usually, no more than one element is looked up or updated at once.

        Given that usage pattern, the difference in performance between using the map │ │ │ │ syntax and the maps module is usually small. Therefore, which one to use is │ │ │ │ @@ -72,18 +72,18 @@ │ │ │ │ choice.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Using Maps as Sets │ │ │ │

      │ │ │ │

      Starting in OTP 24, the sets module has an option to represent sets as maps. │ │ │ │ -Examples:

      1> sets:new([{version,2}]).
      │ │ │ │ -#{}
      │ │ │ │ -2> sets:from_list([x,y,z], [{version,2}]).
      │ │ │ │ -#{x => [],y => [],z => []}

      sets backed by maps is generally the most efficient set representation, with a │ │ │ │ +Examples:

      1> sets:new([{version,2}]).
      │ │ │ │ +#{}
      │ │ │ │ +2> sets:from_list([x,y,z], [{version,2}]).
      │ │ │ │ +#{x => [],y => [],z => []}

      sets backed by maps is generally the most efficient set representation, with a │ │ │ │ few possible exceptions:

      • ordsets:intersection/2 can be more efficient than sets:intersection/2. If │ │ │ │ the intersection operation is frequently used and operations that operate on a │ │ │ │ single element in a set (such as is_element/2) are avoided, ordsets can │ │ │ │ be a better choice than sets.
      • If the intersection operation is frequently used and operations that operate │ │ │ │ on a single element in a set (such as is_element/2) must also be efficient, │ │ │ │ gb_sets can potentially be a better choice than sets.
      • If the elements of the set are integers in a fairly compact range, the set can │ │ │ │ be represented as an integer where each bit represents an element in the set. │ │ │ │ @@ -108,18 +108,18 @@ │ │ │ │ for the runtime system).

      • N - The number of elements in the map.

      • Keys - A tuple with keys of the map: {Key1,...,KeyN}. The keys are │ │ │ │ sorted.

      • Value1 - The value corresponding to the first key in the key tuple.

      • ValueN - The value corresponding to the last key in the key tuple.

      As an example, let us look at how the map #{a => foo, z => bar} is │ │ │ │ represented:

      01234
      FLATMAP2{a,z}foobar

      Table: #{a => foo, z => bar}

      Let us update the map: M#{q => baz}. The map now looks like this:

      012345
      FLATMAP3{a,q,z}foobazbar

      Table: #{a => foo, q => baz, z => bar}

      Finally, change the value of one element: M#{z := bird}. The map now looks │ │ │ │ like this:

      012345
      FLATMAP3{a,q,z}foobazbird

      Table: #{a => foo, q => baz, z => bird}

      When the value for an existing key is updated, the key tuple is not updated, │ │ │ │ allowing the key tuple to be shared with other instances of the map that have │ │ │ │ the same keys. In fact, the key tuple can be shared between all maps with the │ │ │ │ same keys with some care. To arrange that, define a function that returns a map. │ │ │ │ -For example:

      new() ->
      │ │ │ │ -    #{a => default, b => default, c => default}.

      Defined like this, the key tuple {a,b,c} will be a global literal. To ensure │ │ │ │ +For example:

      new() ->
      │ │ │ │ +    #{a => default, b => default, c => default}.

      Defined like this, the key tuple {a,b,c} will be a global literal. To ensure │ │ │ │ that the key tuple is shared when creating an instance of the map, always call │ │ │ │ -new() and modify the returned map:

          (SOME_MODULE:new())#{a := 42}.

      Using the map syntax with small maps is particularly efficient. As long as the │ │ │ │ +new() and modify the returned map:

          (SOME_MODULE:new())#{a := 42}.

      Using the map syntax with small maps is particularly efficient. As long as the │ │ │ │ keys are known at compile-time, the map is updated in one go, making the time to │ │ │ │ update a map essentially constant regardless of the number of keys updated. The │ │ │ │ same goes for matching. (When the keys are variables, one or more of the keys │ │ │ │ could be identical, so the operations need to be performed sequentially from │ │ │ │ left to right.)

      The memory size for a small map is the size of all keys and values plus 5 words. │ │ │ │ See Memory for more information about memory sizes.

      │ │ │ │ │ │ │ │ @@ -146,21 +146,21 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Using the Map Syntax │ │ │ │

      │ │ │ │

      Using the map syntax is usually slightly more efficient than using the │ │ │ │ corresponding function in the maps module.

      The gain in efficiency for the map syntax is more noticeable for the following │ │ │ │ -operations that can only be achieved using the map syntax:

      • Matching multiple literal keys
      • Updating multiple literal keys
      • Adding multiple literal keys to a map

      For example:

      DO

      Map = Map1#{x := X, y := Y, z := Z}

      DO NOT

      Map2 = maps:update(x, X, Map1),
      │ │ │ │ -Map3 = maps:update(y, Y, Map2),
      │ │ │ │ -Map = maps:update(z, Z, Map3)

      If the map is a small map, the first example runs roughly three times as fast.

      Note that for variable keys, the elements are updated sequentially from left to │ │ │ │ -right. For example, given the following update with variable keys:

      Map = Map1#{Key1 := X, Key2 := Y, Key3 := Z}

      the compiler rewrites it like this to ensure that the updates are applied from │ │ │ │ -left to right:

      Map2 = Map1#{Key1 := X},
      │ │ │ │ -Map3 = Map2#{Key2 := Y},
      │ │ │ │ -Map = Map3#{Key3 := Z}

      If a key is known to exist in a map, using the := operator is slightly more │ │ │ │ +operations that can only be achieved using the map syntax:

      • Matching multiple literal keys
      • Updating multiple literal keys
      • Adding multiple literal keys to a map

      For example:

      DO

      Map = Map1#{x := X, y := Y, z := Z}

      DO NOT

      Map2 = maps:update(x, X, Map1),
      │ │ │ │ +Map3 = maps:update(y, Y, Map2),
      │ │ │ │ +Map = maps:update(z, Z, Map3)

      If the map is a small map, the first example runs roughly three times as fast.

      Note that for variable keys, the elements are updated sequentially from left to │ │ │ │ +right. For example, given the following update with variable keys:

      Map = Map1#{Key1 := X, Key2 := Y, Key3 := Z}

      the compiler rewrites it like this to ensure that the updates are applied from │ │ │ │ +left to right:

      Map2 = Map1#{Key1 := X},
      │ │ │ │ +Map3 = Map2#{Key2 := Y},
      │ │ │ │ +Map = Map3#{Key3 := Z}

      If a key is known to exist in a map, using the := operator is slightly more │ │ │ │ efficient than using the => operator for a small map.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Using the Functions in the maps Module │ │ │ │

      │ │ │ │

      Here follows some notes about most of the functions in the maps module. For │ │ │ │ @@ -211,23 +211,23 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ maps:get/3 │ │ │ │ │ │ │ │

      As an optimization, the compiler will rewrite a call to maps:get/3 to Erlang │ │ │ │ code similar to the following:

      Result = case Map of
      │ │ │ │ -             #{Key := Value} -> Value;
      │ │ │ │ -             #{} -> Default
      │ │ │ │ +             #{Key := Value} -> Value;
      │ │ │ │ +             #{} -> Default
      │ │ │ │           end

      This is reasonably efficient, but if a small map is used as an alternative to │ │ │ │ using a record it is often better not to rely on default values as it prevents │ │ │ │ sharing of keys, which may in the end use more memory than what you save from │ │ │ │ not storing default values in the map.

      If default values are nevertheless required, instead of calling maps:get/3 │ │ │ │ multiple times, consider putting the default values in a map and merging that │ │ │ │ -map with the other map:

      DefaultMap = #{Key1 => Value2, Key2 => Value2, ..., KeyN => ValueN},
      │ │ │ │ -MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)

      This helps share keys between the default map and the one you applied defaults │ │ │ │ +map with the other map:

      DefaultMap = #{Key1 => Value2, Key2 => Value2, ..., KeyN => ValueN},
      │ │ │ │ +MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)

      This helps share keys between the default map and the one you applied defaults │ │ │ │ to, as long as the default map contains all the keys that will ever be used │ │ │ │ and not just the ones with default values. Whether this is faster than calling │ │ │ │ maps:get/3 multiple times depends on the size of the map and the number of │ │ │ │ default values.

      Change

      Before OTP 26.0 maps:get/3 was implemented by calling the function instead │ │ │ │ of rewriting it as an Erlang expression. It is now slightly faster but can no │ │ │ │ longer be traced.

      │ │ │ │ │ │ │ ├── OEBPS/macros.xhtml │ │ │ │ @@ -22,56 +22,56 @@ │ │ │ │

      │ │ │ │

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ File Inclusion │ │ │ │

      │ │ │ │ -

      A file can be included as follows:

      -include(File).
      │ │ │ │ --include_lib(File).

      File, a string, is to point out a file. The contents of this file are included │ │ │ │ +

      A file can be included as follows:

      -include(File).
      │ │ │ │ +-include_lib(File).

      File, a string, is to point out a file. The contents of this file are included │ │ │ │ as is, at the position of the directive.

      Include files are typically used for record and macro definitions that are │ │ │ │ shared by several modules. It is recommended to use the file name extension │ │ │ │ .hrl for include files.

      File can start with a path component $VAR, for some string VAR. If that is │ │ │ │ the case, the value of the environment variable VAR as returned by │ │ │ │ os:getenv(VAR) is substituted for $VAR. If os:getenv(VAR) returns false, │ │ │ │ $VAR is left as is.

      If the filename File is absolute (possibly after variable substitution), the │ │ │ │ include file with that name is included. Otherwise, the specified file is │ │ │ │ searched for in the following directories, and in this order:

      1. The current working directory
      2. The directory where the module is being compiled
      3. The directories given by the include option

      For details, see erlc in ERTS and │ │ │ │ -compile in Compiler.

      Examples:

      -include("my_records.hrl").
      │ │ │ │ --include("incdir/my_records.hrl").
      │ │ │ │ --include("/home/user/proj/my_records.hrl").
      │ │ │ │ --include("$PROJ_ROOT/my_records.hrl").

      include_lib is similar to include, but is not to point out an absolute file. │ │ │ │ +compile in Compiler.

      Examples:

      -include("my_records.hrl").
      │ │ │ │ +-include("incdir/my_records.hrl").
      │ │ │ │ +-include("/home/user/proj/my_records.hrl").
      │ │ │ │ +-include("$PROJ_ROOT/my_records.hrl").

      include_lib is similar to include, but is not to point out an absolute file. │ │ │ │ Instead, the first path component (possibly after variable substitution) is │ │ │ │ -assumed to be the name of an application.

      Example:

      -include_lib("kernel/include/file.hrl").

      The code server uses code:lib_dir(kernel) to find the directory of the current │ │ │ │ +assumed to be the name of an application.

      Example:

      -include_lib("kernel/include/file.hrl").

      The code server uses code:lib_dir(kernel) to find the directory of the current │ │ │ │ (latest) version of Kernel, and then the subdirectory include is searched for │ │ │ │ the file file.hrl.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Defining and Using Macros │ │ │ │

      │ │ │ │ -

      A macro is defined as follows:

      -define(Const, Replacement).
      │ │ │ │ --define(Func(Var1,...,VarN), Replacement).

      A macro definition can be placed anywhere among the attributes and function │ │ │ │ +

      A macro is defined as follows:

      -define(Const, Replacement).
      │ │ │ │ +-define(Func(Var1,...,VarN), Replacement).

      A macro definition can be placed anywhere among the attributes and function │ │ │ │ declarations of a module, but the definition must come before any usage of the │ │ │ │ macro.

      If a macro is used in several modules, it is recommended that the macro │ │ │ │ definition is placed in an include file.

      A macro is used as follows:

      ?Const
      │ │ │ │  ?Func(Arg1,...,ArgN)

      Macros are expanded during compilation. A simple macro ?Const is replaced with │ │ │ │ -Replacement.

      Example:

      -define(TIMEOUT, 200).
      │ │ │ │ +Replacement.

      Example:

      -define(TIMEOUT, 200).
      │ │ │ │  ...
      │ │ │ │ -call(Request) ->
      │ │ │ │ -    server:call(refserver, Request, ?TIMEOUT).

      This is expanded to:

      call(Request) ->
      │ │ │ │ -    server:call(refserver, Request, 200).

      A macro ?Func(Arg1,...,ArgN) is replaced with Replacement, where all │ │ │ │ +call(Request) -> │ │ │ │ + server:call(refserver, Request, ?TIMEOUT).

      This is expanded to:

      call(Request) ->
      │ │ │ │ +    server:call(refserver, Request, 200).

      A macro ?Func(Arg1,...,ArgN) is replaced with Replacement, where all │ │ │ │ occurrences of a variable Var from the macro definition are replaced with the │ │ │ │ -corresponding argument Arg.

      Example:

      -define(MACRO1(X, Y), {a, X, b, Y}).
      │ │ │ │ +corresponding argument Arg.

      Example:

      -define(MACRO1(X, Y), {a, X, b, Y}).
      │ │ │ │  ...
      │ │ │ │ -bar(X) ->
      │ │ │ │ -    ?MACRO1(a, b),
      │ │ │ │ -    ?MACRO1(X, 123)

      This is expanded to:

      bar(X) ->
      │ │ │ │ -    {a,a,b,b},
      │ │ │ │ -    {a,X,b,123}.

      It is good programming practice, but not mandatory, to ensure that a macro │ │ │ │ +bar(X) -> │ │ │ │ + ?MACRO1(a, b), │ │ │ │ + ?MACRO1(X, 123)

      This is expanded to:

      bar(X) ->
      │ │ │ │ +    {a,a,b,b},
      │ │ │ │ +    {a,X,b,123}.

      It is good programming practice, but not mandatory, to ensure that a macro │ │ │ │ definition is a valid Erlang syntactic form.

      To view the result of macro expansion, a module can be compiled with the 'P' │ │ │ │ option. compile:file(File, ['P']). This produces a listing of the parsed code │ │ │ │ after preprocessing and parse transforms, in the file File.P.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Predefined Macros │ │ │ │ @@ -90,29 +90,29 @@ │ │ │ │ │ │ │ │ │ │ │ │ Macros Overloading │ │ │ │

      │ │ │ │

      It is possible to overload macros, except for predefined macros. An overloaded │ │ │ │ macro has more than one definition, each with a different number of arguments.

      Change

      Support for overloading of macros was added in Erlang 5.7.5/OTP R13B04.

      A macro ?Func(Arg1,...,ArgN) with a (possibly empty) list of arguments results │ │ │ │ in an error message if there is at least one definition of Func with │ │ │ │ -arguments, but none with N arguments.

      Assuming these definitions:

      -define(F0(), c).
      │ │ │ │ --define(F1(A), A).
      │ │ │ │ --define(C, m:f).

      the following does not work:

      f0() ->
      │ │ │ │ +arguments, but none with N arguments.

      Assuming these definitions:

      -define(F0(), c).
      │ │ │ │ +-define(F1(A), A).
      │ │ │ │ +-define(C, m:f).

      the following does not work:

      f0() ->
      │ │ │ │      ?F0. % No, an empty list of arguments expected.
      │ │ │ │  
      │ │ │ │ -f1(A) ->
      │ │ │ │ -    ?F1(A, A). % No, exactly one argument expected.

      On the other hand,

      f() ->
      │ │ │ │ -    ?C().

      is expanded to

      f() ->
      │ │ │ │ -    m:f().

      │ │ │ │ +f1(A) -> │ │ │ │ + ?F1(A, A). % No, exactly one argument expected.

      On the other hand,

      f() ->
      │ │ │ │ +    ?C().

      is expanded to

      f() ->
      │ │ │ │ +    m:f().

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Removing a macro definition │ │ │ │

      │ │ │ │ -

      A definition of macro can be removed as follows:

      -undef(Macro).

      │ │ │ │ +

      A definition of macro can be removed as follows:

      -undef(Macro).

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Conditional Compilation │ │ │ │

      │ │ │ │

      The following macro directives support conditional compilation:

      • -ifdef(Macro). - Evaluate the following lines only if Macro is │ │ │ │ defined.

      • -ifndef(Macro). - Evaluate the following lines only if Macro is not │ │ │ │ @@ -124,43 +124,43 @@ │ │ │ │ true, and the Condition evaluates to true, the lines following the elif │ │ │ │ are evaluated instead.

      • -endif. - Specifies the end of a series of control flow directives.

      Note

      Macro directives cannot be used inside functions.

      Syntactically, the Condition in if and elif must be a │ │ │ │ guard expression. Other constructs (such as │ │ │ │ a case expression) result in a compilation error.

      As opposed to the standard guard expressions, an expression in an if and │ │ │ │ elif also supports calling the psuedo-function defined(Name), which tests │ │ │ │ whether the Name argument is the name of a previously defined macro. │ │ │ │ defined(Name) evaluates to true if the macro is defined and false │ │ │ │ -otherwise. An attempt to call other functions results in a compilation error.

      Example:

      -module(m).
      │ │ │ │ +otherwise. An attempt to call other functions results in a compilation error.

      Example:

      -module(m).
      │ │ │ │  ...
      │ │ │ │  
      │ │ │ │ --ifdef(debug).
      │ │ │ │ --define(LOG(X), io:format("{~p,~p}: ~p~n", [?MODULE,?LINE,X])).
      │ │ │ │ +-ifdef(debug).
      │ │ │ │ +-define(LOG(X), io:format("{~p,~p}: ~p~n", [?MODULE,?LINE,X])).
      │ │ │ │  -else.
      │ │ │ │ --define(LOG(X), true).
      │ │ │ │ +-define(LOG(X), true).
      │ │ │ │  -endif.
      │ │ │ │  
      │ │ │ │  ...

      When trace output is desired, debug is to be defined when the module m is │ │ │ │ compiled:

      % erlc -Ddebug m.erl
      │ │ │ │  
      │ │ │ │  or
      │ │ │ │  
      │ │ │ │ -1> c(m, {d, debug}).
      │ │ │ │ -{ok,m}

      ?LOG(Arg) is then expanded to a call to io:format/2 and provide the user │ │ │ │ -with some simple trace output.

      Example:

      -module(m)
      │ │ │ │ +1> c(m, {d, debug}).
      │ │ │ │ +{ok,m}

      ?LOG(Arg) is then expanded to a call to io:format/2 and provide the user │ │ │ │ +with some simple trace output.

      Example:

      -module(m)
      │ │ │ │  ...
      │ │ │ │ --if(?OTP_RELEASE >= 25).
      │ │ │ │ +-if(?OTP_RELEASE >= 25).
      │ │ │ │  %% Code that will work in OTP 25 or higher
      │ │ │ │ --elif(?OTP_RELEASE >= 26).
      │ │ │ │ +-elif(?OTP_RELEASE >= 26).
      │ │ │ │  %% Code that will work in OTP 26 or higher
      │ │ │ │  -else.
      │ │ │ │  %% Code that will work in OTP 24 or lower.
      │ │ │ │  -endif.
      │ │ │ │  ...

      This code uses the OTP_RELEASE macro to conditionally select code depending on │ │ │ │ -release.

      Example:

      -module(m)
      │ │ │ │ +release.

      Example:

      -module(m)
      │ │ │ │  ...
      │ │ │ │ --if(?OTP_RELEASE >= 26 andalso defined(debug)).
      │ │ │ │ +-if(?OTP_RELEASE >= 26 andalso defined(debug)).
      │ │ │ │  %% Debugging code that requires OTP 26 or later.
      │ │ │ │  -else.
      │ │ │ │  %% Non-debug code that works in any release.
      │ │ │ │  -endif.
      │ │ │ │  ...

      This code uses the OTP_RELEASE macro and defined(debug) to compile debug │ │ │ │ code only for OTP 26 or later.

      │ │ │ │ │ │ │ │ @@ -175,40 +175,40 @@ │ │ │ │ used. In practice this means it should appear before any -export(..) or record │ │ │ │ definitions.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ -error() and -warning() directives │ │ │ │

      │ │ │ │ -

      The directive -error(Term) causes a compilation error.

      Example:

      -module(t).
      │ │ │ │ --export([version/0]).
      │ │ │ │ +

      The directive -error(Term) causes a compilation error.

      Example:

      -module(t).
      │ │ │ │ +-export([version/0]).
      │ │ │ │  
      │ │ │ │ --ifdef(VERSION).
      │ │ │ │ -version() -> ?VERSION.
      │ │ │ │ +-ifdef(VERSION).
      │ │ │ │ +version() -> ?VERSION.
      │ │ │ │  -else.
      │ │ │ │ --error("Macro VERSION must be defined.").
      │ │ │ │ -version() -> "".
      │ │ │ │ +-error("Macro VERSION must be defined.").
      │ │ │ │ +version() -> "".
      │ │ │ │  -endif.

      The error message will look like this:

      % erlc t.erl
      │ │ │ │ -t.erl:7: -error("Macro VERSION must be defined.").

      The directive -warning(Term) causes a compilation warning.

      Example:

      -module(t).
      │ │ │ │ --export([version/0]).
      │ │ │ │ +t.erl:7: -error("Macro VERSION must be defined.").

      The directive -warning(Term) causes a compilation warning.

      Example:

      -module(t).
      │ │ │ │ +-export([version/0]).
      │ │ │ │  
      │ │ │ │ --ifndef(VERSION).
      │ │ │ │ --warning("Macro VERSION not defined -- using default version.").
      │ │ │ │ --define(VERSION, "0").
      │ │ │ │ +-ifndef(VERSION).
      │ │ │ │ +-warning("Macro VERSION not defined -- using default version.").
      │ │ │ │ +-define(VERSION, "0").
      │ │ │ │  -endif.
      │ │ │ │ -version() -> ?VERSION.

      The warning message will look like this:

      % erlc t.erl
      │ │ │ │ +version() -> ?VERSION.

      The warning message will look like this:

      % erlc t.erl
      │ │ │ │  t.erl:5: Warning: -warning("Macro VERSION not defined -- using default version.").

      Change

      The -error() and -warning() directives were added in Erlang/OTP 19.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Stringifying Macro Arguments │ │ │ │

      │ │ │ │

      The construction ??Arg, where Arg is a macro argument, is expanded to a │ │ │ │ string containing the tokens of the argument. This is similar to the #arg │ │ │ │ -stringifying construction in C.

      Example:

      -define(TESTCALL(Call), io:format("Call ~s: ~w~n", [??Call, Call])).
      │ │ │ │ +stringifying construction in C.

      Example:

      -define(TESTCALL(Call), io:format("Call ~s: ~w~n", [??Call, Call])).
      │ │ │ │  
      │ │ │ │ -?TESTCALL(myfunction(1,2)),
      │ │ │ │ -?TESTCALL(you:function(2,1)).

      results in

      io:format("Call ~s: ~w~n",["myfunction ( 1 , 2 )",myfunction(1,2)]),
      │ │ │ │ -io:format("Call ~s: ~w~n",["you : function ( 2 , 1 )",you:function(2,1)]).

      That is, a trace output, with both the function called and the resulting value.

      │ │ │ │ +
      ?TESTCALL(myfunction(1,2)), │ │ │ │ +?TESTCALL(you:function(2,1)).

      results in

      io:format("Call ~s: ~w~n",["myfunction ( 1 , 2 )",myfunction(1,2)]),
      │ │ │ │ +io:format("Call ~s: ~w~n",["you : function ( 2 , 1 )",you:function(2,1)]).

      That is, a trace output, with both the function called and the resulting value.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/listhandling.xhtml │ │ │ │ @@ -25,101 +25,101 @@ │ │ │ │ │ │ │ │ │ │ │ │ Creating a List │ │ │ │

      │ │ │ │

      Lists can only be built starting from the end and attaching list elements at the │ │ │ │ beginning. If you use the ++ operator as follows, a new list is created that │ │ │ │ is a copy of the elements in List1, followed by List2:

      List1 ++ List2

      Looking at how lists:append/2 or ++ would be implemented in plain Erlang, │ │ │ │ -clearly the first list is copied:

      append([H|T], Tail) ->
      │ │ │ │ -    [H|append(T, Tail)];
      │ │ │ │ -append([], Tail) ->
      │ │ │ │ +clearly the first list is copied:

      append([H|T], Tail) ->
      │ │ │ │ +    [H|append(T, Tail)];
      │ │ │ │ +append([], Tail) ->
      │ │ │ │      Tail.

      When recursing and building a list, it is important to ensure that you attach │ │ │ │ the new elements to the beginning of the list. In this way, you will build one │ │ │ │ -list, not hundreds or thousands of copies of the growing result list.

      Let us first see how it is not to be done:

      DO NOT

      bad_fib(N) ->
      │ │ │ │ -    bad_fib(N, 0, 1, []).
      │ │ │ │ +list, not hundreds or thousands of copies of the growing result list.

      Let us first see how it is not to be done:

      DO NOT

      bad_fib(N) ->
      │ │ │ │ +    bad_fib(N, 0, 1, []).
      │ │ │ │  
      │ │ │ │ -bad_fib(0, _Current, _Next, Fibs) ->
      │ │ │ │ +bad_fib(0, _Current, _Next, Fibs) ->
      │ │ │ │      Fibs;
      │ │ │ │ -bad_fib(N, Current, Next, Fibs) ->
      │ │ │ │ -    bad_fib(N - 1, Next, Current + Next, Fibs ++ [Current]).

      Here more than one list is built. In each iteration step a new list is created │ │ │ │ +bad_fib(N, Current, Next, Fibs) -> │ │ │ │ + bad_fib(N - 1, Next, Current + Next, Fibs ++ [Current]).

      Here more than one list is built. In each iteration step a new list is created │ │ │ │ that is one element longer than the new previous list.

      To avoid copying the result in each iteration, build the list in reverse order │ │ │ │ -and reverse the list when you are done:

      DO

      tail_recursive_fib(N) ->
      │ │ │ │ -    tail_recursive_fib(N, 0, 1, []).
      │ │ │ │ +and reverse the list when you are done:

      DO

      tail_recursive_fib(N) ->
      │ │ │ │ +    tail_recursive_fib(N, 0, 1, []).
      │ │ │ │  
      │ │ │ │ -tail_recursive_fib(0, _Current, _Next, Fibs) ->
      │ │ │ │ -    lists:reverse(Fibs);
      │ │ │ │ -tail_recursive_fib(N, Current, Next, Fibs) ->
      │ │ │ │ -    tail_recursive_fib(N - 1, Next, Current + Next, [Current|Fibs]).

      │ │ │ │ +tail_recursive_fib(0, _Current, _Next, Fibs) -> │ │ │ │ + lists:reverse(Fibs); │ │ │ │ +tail_recursive_fib(N, Current, Next, Fibs) -> │ │ │ │ + tail_recursive_fib(N - 1, Next, Current + Next, [Current|Fibs]).

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ List Comprehensions │ │ │ │

      │ │ │ │ -

      A list comprehension:

      [Expr(E) || E <- List]

      is basically translated to a local function:

      'lc^0'([E|Tail], Expr) ->
      │ │ │ │ -    [Expr(E)|'lc^0'(Tail, Expr)];
      │ │ │ │ -'lc^0'([], _Expr) -> [].

      If the result of the list comprehension will obviously not be used, a list │ │ │ │ -will not be constructed. For example, in this code:

      [io:put_chars(E) || E <- List],
      │ │ │ │ +

      A list comprehension:

      [Expr(E) || E <- List]

      is basically translated to a local function:

      'lc^0'([E|Tail], Expr) ->
      │ │ │ │ +    [Expr(E)|'lc^0'(Tail, Expr)];
      │ │ │ │ +'lc^0'([], _Expr) -> [].

      If the result of the list comprehension will obviously not be used, a list │ │ │ │ +will not be constructed. For example, in this code:

      [io:put_chars(E) || E <- List],
      │ │ │ │  ok.

      or in this code:

      case Var of
      │ │ │ │      ... ->
      │ │ │ │ -        [io:put_chars(E) || E <- List];
      │ │ │ │ +        [io:put_chars(E) || E <- List];
      │ │ │ │      ... ->
      │ │ │ │  end,
      │ │ │ │ -some_function(...),

      the value is not assigned to a variable, not passed to another function, and not │ │ │ │ +some_function(...),

      the value is not assigned to a variable, not passed to another function, and not │ │ │ │ returned. This means that there is no need to construct a list and the compiler │ │ │ │ -will simplify the code for the list comprehension to:

      'lc^0'([E|Tail], Expr) ->
      │ │ │ │ -    Expr(E),
      │ │ │ │ -    'lc^0'(Tail, Expr);
      │ │ │ │ -'lc^0'([], _Expr) -> [].

      The compiler also understands that assigning to _ means that the value will │ │ │ │ -not be used. Therefore, the code in the following example will also be optimized:

      _ = [io:put_chars(E) || E <- List],
      │ │ │ │ +will simplify the code for the list comprehension to:

      'lc^0'([E|Tail], Expr) ->
      │ │ │ │ +    Expr(E),
      │ │ │ │ +    'lc^0'(Tail, Expr);
      │ │ │ │ +'lc^0'([], _Expr) -> [].

      The compiler also understands that assigning to _ means that the value will │ │ │ │ +not be used. Therefore, the code in the following example will also be optimized:

      _ = [io:put_chars(E) || E <- List],
      │ │ │ │  ok.

      │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Deep and Flat Lists │ │ │ │

      │ │ │ │

      lists:flatten/1 builds an entirely new list. It is therefore expensive, and │ │ │ │ even more expensive than the ++ operator (which copies its left argument, │ │ │ │ but not its right argument).

      In the following situations it is unnecessary to call lists:flatten/1:

      • When sending data to a port. Ports understand deep lists so there is no reason │ │ │ │ to flatten the list before sending it to the port.
      • When calling BIFs that accept deep lists, such as │ │ │ │ list_to_binary/1 or │ │ │ │ iolist_to_binary/1.
      • When you know that your list is only one level deep. Use lists:append/1 │ │ │ │ -instead.

      Examples:

      DO

      port_command(Port, DeepList)

      DO NOT

      port_command(Port, lists:flatten(DeepList))

      A common way to send a zero-terminated string to a port is the following:

      DO NOT

      TerminatedStr = String ++ [0],
      │ │ │ │ -port_command(Port, TerminatedStr)

      Instead:

      DO

      TerminatedStr = [String, 0],
      │ │ │ │ -port_command(Port, TerminatedStr)

      DO

      1> lists:append([[1], [2], [3]]).
      │ │ │ │ -[1,2,3]

      DO NOT

      1> lists:flatten([[1], [2], [3]]).
      │ │ │ │ -[1,2,3]

      │ │ │ │ +instead.

    Examples:

    DO

    port_command(Port, DeepList)

    DO NOT

    port_command(Port, lists:flatten(DeepList))

    A common way to send a zero-terminated string to a port is the following:

    DO NOT

    TerminatedStr = String ++ [0],
    │ │ │ │ +port_command(Port, TerminatedStr)

    Instead:

    DO

    TerminatedStr = [String, 0],
    │ │ │ │ +port_command(Port, TerminatedStr)

    DO

    1> lists:append([[1], [2], [3]]).
    │ │ │ │ +[1,2,3]

    DO NOT

    1> lists:flatten([[1], [2], [3]]).
    │ │ │ │ +[1,2,3]

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Recursive List Functions │ │ │ │

    │ │ │ │

    There are two basic ways to write a function that traverses a list and │ │ │ │ produces a new list.

    The first way is writing a body-recursive function:

    %% Add 42 to each integer in the list.
    │ │ │ │ -add_42_body([H|T]) ->
    │ │ │ │ -    [H + 42 | add_42_body(T)];
    │ │ │ │ -add_42_body([]) ->
    │ │ │ │ -    [].

    The second way is writing a tail-recursive function:

    %% Add 42 to each integer in the list.
    │ │ │ │ -add_42_tail(List) ->
    │ │ │ │ -    add_42_tail(List, []).
    │ │ │ │ +add_42_body([H|T]) ->
    │ │ │ │ +    [H + 42 | add_42_body(T)];
    │ │ │ │ +add_42_body([]) ->
    │ │ │ │ +    [].

    The second way is writing a tail-recursive function:

    %% Add 42 to each integer in the list.
    │ │ │ │ +add_42_tail(List) ->
    │ │ │ │ +    add_42_tail(List, []).
    │ │ │ │  
    │ │ │ │ -add_42_tail([H|T], Acc) ->
    │ │ │ │ -    add_42_tail(T, [H + 42 | Acc]);
    │ │ │ │ -add_42_tail([], Acc) ->
    │ │ │ │ -    lists:reverse(Acc).

    In early version of Erlang the tail-recursive function would typically │ │ │ │ +add_42_tail([H|T], Acc) -> │ │ │ │ + add_42_tail(T, [H + 42 | Acc]); │ │ │ │ +add_42_tail([], Acc) -> │ │ │ │ + lists:reverse(Acc).

In early version of Erlang the tail-recursive function would typically │ │ │ │ be more efficient. In modern versions of Erlang, there is usually not │ │ │ │ much difference in performance between a body-recursive list function and │ │ │ │ tail-recursive function that reverses the list at the end. Therefore, │ │ │ │ concentrate on writing beautiful code and forget about the performance │ │ │ │ of your list functions. In the time-critical parts of your code, │ │ │ │ measure before rewriting your code.

For a thorough discussion about tail and body recursion, see │ │ │ │ Erlang's Tail Recursion is Not a Silver Bullet.

Note

This section is about list functions that construct lists. A tail-recursive │ │ │ │ function that does not construct a list runs in constant space, while the │ │ │ │ corresponding body-recursive function uses stack space proportional to the │ │ │ │ length of the list.

For example, a function that sums a list of integers, is not to be written as │ │ │ │ -follows:

DO NOT

recursive_sum([H|T]) -> H+recursive_sum(T);
│ │ │ │ -recursive_sum([])    -> 0.

Instead:

DO

sum(L) -> sum(L, 0).
│ │ │ │ +follows:

DO NOT

recursive_sum([H|T]) -> H+recursive_sum(T);
│ │ │ │ +recursive_sum([])    -> 0.

Instead:

DO

sum(L) -> sum(L, 0).
│ │ │ │  
│ │ │ │ -sum([H|T], Sum) -> sum(T, Sum + H);
│ │ │ │ -sum([], Sum)    -> Sum.
│ │ │ │ +
sum([H|T], Sum) -> sum(T, Sum + H); │ │ │ │ +sum([], Sum) -> Sum.
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/list_comprehensions.xhtml │ │ │ │ @@ -22,33 +22,33 @@ │ │ │ │ │ │ │ │

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Simple Examples │ │ │ │

│ │ │ │ -

This section starts with a simple example, showing a generator and a filter:

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

This is read as follows: The list of X such that X is taken from the list │ │ │ │ +

This section starts with a simple example, showing a generator and a filter:

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

This is read as follows: The list of X such that X is taken from the list │ │ │ │ [1,2,a,...] and X is greater than 3.

The notation X <- [1,2,a,...] is a generator and the expression X > 3 is a │ │ │ │ filter.

An additional filter, is_integer(X), can be added to │ │ │ │ -restrict the result to integers:

> [X || X <- [1,2,a,3,4,b,5,6], is_integer(X), X > 3].
│ │ │ │ -[4,5,6]

Generators can be combined. For example, the Cartesian product of two lists can │ │ │ │ -be written as follows:

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

│ │ │ │ +restrict the result to integers:

> [X || X <- [1,2,a,3,4,b,5,6], is_integer(X), X > 3].
│ │ │ │ +[4,5,6]

Generators can be combined. For example, the Cartesian product of two lists can │ │ │ │ +be written as follows:

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

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Quick Sort │ │ │ │

│ │ │ │ -

The well-known quick sort routine can be written as follows:

sort([]) -> [];
│ │ │ │ -sort([_] = L) -> L;
│ │ │ │ -sort([Pivot|T]) ->
│ │ │ │ -    sort([ X || X <- T, X < Pivot]) ++
│ │ │ │ -    [Pivot] ++
│ │ │ │ -    sort([ X || X <- T, X >= Pivot]).

The expression [X || X <- T, X < Pivot] is the list of all elements in T │ │ │ │ +

The well-known quick sort routine can be written as follows:

sort([]) -> [];
│ │ │ │ +sort([_] = L) -> L;
│ │ │ │ +sort([Pivot|T]) ->
│ │ │ │ +    sort([ X || X <- T, X < Pivot]) ++
│ │ │ │ +    [Pivot] ++
│ │ │ │ +    sort([ X || X <- T, X >= Pivot]).

The expression [X || X <- T, X < Pivot] is the list of all elements in T │ │ │ │ that are less than Pivot.

[X || X <- T, X >= Pivot] is the list of all elements in T that are greater │ │ │ │ than or equal to Pivot.

With the algorithm above, a list is sorted as follows:

  • A list with zero or one element is trivially sorted.
  • For lists with more than one element:
    1. The first element in the list is isolated as the pivot element.
    2. The remaining list is partitioned into two sublists, such that:
    • The first sublist contains all elements that are smaller than the pivot │ │ │ │ element.
    • The second sublist contains all elements that are greater than or equal to │ │ │ │ the pivot element.
    1. The sublists are recursively sorted by the same algorithm and the results │ │ │ │ are combined, resulting in a list consisting of:
    • All elements from the first sublist, that is all elements smaller than the │ │ │ │ pivot element, in sorted order.
    • The pivot element.
    • All elements from the second sublist, that is all elements greater than or │ │ │ │ equal to the pivot element, in sorted order.

Note

While the sorting algorithm as shown above serves as a nice example to │ │ │ │ @@ -56,90 +56,90 @@ │ │ │ │ lists module contains sorting functions that are implemented in a more │ │ │ │ efficient way.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Permutations │ │ │ │

│ │ │ │ -

The following example generates all permutations of the elements in a list:

perms([]) -> [[]];
│ │ │ │ -perms(L)  -> [[H|T] || H <- L, T <- perms(L--[H])].

This takes H from L in all possible ways. The result is the set of all lists │ │ │ │ +

The following example generates all permutations of the elements in a list:

perms([]) -> [[]];
│ │ │ │ +perms(L)  -> [[H|T] || H <- L, T <- perms(L--[H])].

This takes H from L in all possible ways. The result is the set of all lists │ │ │ │ [H|T], where T is the set of all possible permutations of L, with H │ │ │ │ -removed:

> perms([b,u,g]).
│ │ │ │ -[[b,u,g],[b,g,u],[u,b,g],[u,g,b],[g,b,u],[g,u,b]]

│ │ │ │ +removed:

> perms([b,u,g]).
│ │ │ │ +[[b,u,g],[b,g,u],[u,b,g],[u,g,b],[g,b,u],[g,u,b]]

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Pythagorean Triplets │ │ │ │

│ │ │ │

Pythagorean triplets are sets of integers {A,B,C} such that │ │ │ │ A**2 + B**2 = C**2.

The function pyth(N) generates a list of all integers {A,B,C} such that │ │ │ │ A**2 + B**2 = C**2 and where the sum of the sides is equal to, or less than, │ │ │ │ -N:

pyth(N) ->
│ │ │ │ -    [ {A,B,C} ||
│ │ │ │ -        A <- lists:seq(1,N),
│ │ │ │ -        B <- lists:seq(1,N),
│ │ │ │ -        C <- lists:seq(1,N),
│ │ │ │ +N:

pyth(N) ->
│ │ │ │ +    [ {A,B,C} ||
│ │ │ │ +        A <- lists:seq(1,N),
│ │ │ │ +        B <- lists:seq(1,N),
│ │ │ │ +        C <- lists:seq(1,N),
│ │ │ │          A+B+C =< N,
│ │ │ │          A*A+B*B == C*C
│ │ │ │ -    ].
> pyth(3).
│ │ │ │ -[].
│ │ │ │ -> pyth(11).
│ │ │ │ -[].
│ │ │ │ -> pyth(12).
│ │ │ │ -[{3,4,5},{4,3,5}]
│ │ │ │ -> pyth(50).
│ │ │ │ -[{3,4,5},
│ │ │ │ - {4,3,5},
│ │ │ │ - {5,12,13},
│ │ │ │ - {6,8,10},
│ │ │ │ - {8,6,10},
│ │ │ │ - {8,15,17},
│ │ │ │ - {9,12,15},
│ │ │ │ - {12,5,13},
│ │ │ │ - {12,9,15},
│ │ │ │ - {12,16,20},
│ │ │ │ - {15,8,17},
│ │ │ │ - {16,12,20}]

The following code reduces the search space and is more efficient:

pyth1(N) ->
│ │ │ │ -   [{A,B,C} ||
│ │ │ │ -       A <- lists:seq(1,N-2),
│ │ │ │ -       B <- lists:seq(A+1,N-1),
│ │ │ │ -       C <- lists:seq(B+1,N),
│ │ │ │ +    ].
> pyth(3).
│ │ │ │ +[].
│ │ │ │ +> pyth(11).
│ │ │ │ +[].
│ │ │ │ +> pyth(12).
│ │ │ │ +[{3,4,5},{4,3,5}]
│ │ │ │ +> pyth(50).
│ │ │ │ +[{3,4,5},
│ │ │ │ + {4,3,5},
│ │ │ │ + {5,12,13},
│ │ │ │ + {6,8,10},
│ │ │ │ + {8,6,10},
│ │ │ │ + {8,15,17},
│ │ │ │ + {9,12,15},
│ │ │ │ + {12,5,13},
│ │ │ │ + {12,9,15},
│ │ │ │ + {12,16,20},
│ │ │ │ + {15,8,17},
│ │ │ │ + {16,12,20}]

The following code reduces the search space and is more efficient:

pyth1(N) ->
│ │ │ │ +   [{A,B,C} ||
│ │ │ │ +       A <- lists:seq(1,N-2),
│ │ │ │ +       B <- lists:seq(A+1,N-1),
│ │ │ │ +       C <- lists:seq(B+1,N),
│ │ │ │         A+B+C =< N,
│ │ │ │ -       A*A+B*B == C*C ].

│ │ │ │ + A*A+B*B == C*C ].

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Simplifications With List Comprehensions │ │ │ │

│ │ │ │

As an example, list comprehensions can be used to simplify some of the functions │ │ │ │ -in lists.erl:

append(L)   ->  [X || L1 <- L, X <- L1].
│ │ │ │ -map(Fun, L) -> [Fun(X) || X <- L].
│ │ │ │ -filter(Pred, L) -> [X || X <- L, Pred(X)].

│ │ │ │ +in lists.erl:

append(L)   ->  [X || L1 <- L, X <- L1].
│ │ │ │ +map(Fun, L) -> [Fun(X) || X <- L].
│ │ │ │ +filter(Pred, L) -> [X || X <- L, Pred(X)].

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Variable Bindings in List Comprehensions │ │ │ │

│ │ │ │

The scope rules for variables that occur in list comprehensions are as follows:

  • All variables that occur in a generator pattern are assumed to be "fresh" │ │ │ │ variables.
  • Any variables that are defined before the list comprehension, and that are │ │ │ │ used in filters, have the values they had before the list comprehension.
  • Variables cannot be exported from a list comprehension.

As an example of these rules, suppose you want to write the function select, │ │ │ │ which selects certain elements from a list of tuples. Suppose you write │ │ │ │ select(X, L) -> [Y || {X, Y} <- L]. with the intention of extracting all │ │ │ │ tuples from L, where the first item is X.

Compiling this gives the following diagnostic:

./FileName.erl:Line: Warning: variable 'X' shadowed in generate

This diagnostic warns that the variable X in the pattern is not the same as │ │ │ │ -the variable X that occurs in the function head.

Evaluating select gives the following result:

> select(b,[{a,1},{b,2},{c,3},{b,7}]).
│ │ │ │ -[1,2,3,7]

This is not the wanted result. To achieve the desired effect, select must be │ │ │ │ -written as follows:

select(X, L) ->  [Y || {X1, Y} <- L, X == X1].

The generator now contains unbound variables and the test has been moved into │ │ │ │ -the filter.

This now works as expected:

> select(b,[{a,1},{b,2},{c,3},{b,7}]).
│ │ │ │ -[2,7]

Also note that a variable in a generator pattern will shadow a variable with the │ │ │ │ -same name bound in a previous generator pattern. For example:

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

A consequence of the rules for importing variables into a list comprehensions is │ │ │ │ +the variable X that occurs in the function head.

Evaluating select gives the following result:

> select(b,[{a,1},{b,2},{c,3},{b,7}]).
│ │ │ │ +[1,2,3,7]

This is not the wanted result. To achieve the desired effect, select must be │ │ │ │ +written as follows:

select(X, L) ->  [Y || {X1, Y} <- L, X == X1].

The generator now contains unbound variables and the test has been moved into │ │ │ │ +the filter.

This now works as expected:

> select(b,[{a,1},{b,2},{c,3},{b,7}]).
│ │ │ │ +[2,7]

Also note that a variable in a generator pattern will shadow a variable with the │ │ │ │ +same name bound in a previous generator pattern. For example:

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

A consequence of the rules for importing variables into a list comprehensions is │ │ │ │ that certain pattern matching operations must be moved into the filters and │ │ │ │ -cannot be written directly in the generators.

To illustrate this, do not write as follows:

f(...) ->
│ │ │ │ +cannot be written directly in the generators.

To illustrate this, do not write as follows:

f(...) ->
│ │ │ │      Y = ...
│ │ │ │ -    [ Expression || PatternInvolving Y  <- Expr, ...]
│ │ │ │ -    ...

Instead, write as follows:

f(...) ->
│ │ │ │ +    [ Expression || PatternInvolving Y  <- Expr, ...]
│ │ │ │ +    ...

Instead, write as follows:

f(...) ->
│ │ │ │      Y = ...
│ │ │ │ -    [ Expression || PatternInvolving Y1  <- Expr, Y == Y1, ...]
│ │ │ │ +    [ Expression || PatternInvolving Y1  <- Expr, Y == Y1, ...]
│ │ │ │      ...
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/install-win32.xhtml │ │ │ │ @@ -200,15 +200,15 @@ │ │ │ │
$ cd erts/emulator │ │ │ │ $ make debug │ │ │ │ $ cd ../etc │ │ │ │ $ make debug │ │ │ │

and sometimes

$ cd $ERL_TOP
│ │ │ │  $ make local_setup
│ │ │ │  

So now when you run $ERL_TOP/erl.exe, you should have a debug compiled │ │ │ │ -emulator, which you will see if you do a:

1> erlang:system_info(system_version).

in the erlang shell. If the returned string contains [debug], you │ │ │ │ +emulator, which you will see if you do a:

1> erlang:system_info(system_version).

in the erlang shell. If the returned string contains [debug], you │ │ │ │ got a debug compiled emulator.

To hack the erlang libraries, you simply do a make opt in the │ │ │ │ specific "applications" directory, like:

$ cd $ERL_TOP/lib/stdlib
│ │ │ │  $ make opt
│ │ │ │  

or even in the source directory...

$ cd $ERL_TOP/lib/stdlib/src
│ │ │ │  $ make opt
│ │ │ │  

Note that you're expected to have a fresh Erlang in your path when │ │ │ │ doing this, preferably the plain 27 you have built in the previous │ │ │ │ @@ -223,19 +223,19 @@ │ │ │ │ :$ERL_TOP/erts/etc/win32/wsl_tools:$ERL_TOP/bootstrap/bin:$PATH │ │ │ │

That should make it possible to rebuild any library without hassle...

If you want to copy a library (an application) newly built, to a │ │ │ │ release area, you do like with the emulator:

$ cd $ERL_TOP/lib/stdlib
│ │ │ │  $ make TESTROOT=/tmp/erlang_release release
│ │ │ │  

Remember that:

That's basically all you need to get going.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Frequently Asked Questions │ │ │ │

│ │ │ │
  • Q: So, now I can build Erlang using GCC on Windows?

    A: No, unfortunately not. You'll need Microsoft's Visual C++ │ │ │ ├── OEBPS/included_applications.xhtml │ │ │ │ @@ -66,72 +66,72 @@ │ │ │ │ belonging to the primary application.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Specifying Included Applications │ │ │ │

    │ │ │ │

    Which applications to include is defined by the included_applications key in │ │ │ │ -the .app file:

    {application, prim_app,
    │ │ │ │ - [{description, "Tree application"},
    │ │ │ │ -  {vsn, "1"},
    │ │ │ │ -  {modules, [prim_app_cb, prim_app_sup, prim_app_server]},
    │ │ │ │ -  {registered, [prim_app_server]},
    │ │ │ │ -  {included_applications, [incl_app]},
    │ │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ │ -  {mod, {prim_app_cb,[]}},
    │ │ │ │ -  {env, [{file, "/usr/local/log"}]}
    │ │ │ │ - ]}.

    │ │ │ │ +the .app file:

    {application, prim_app,
    │ │ │ │ + [{description, "Tree application"},
    │ │ │ │ +  {vsn, "1"},
    │ │ │ │ +  {modules, [prim_app_cb, prim_app_sup, prim_app_server]},
    │ │ │ │ +  {registered, [prim_app_server]},
    │ │ │ │ +  {included_applications, [incl_app]},
    │ │ │ │ +  {applications, [kernel, stdlib, sasl]},
    │ │ │ │ +  {mod, {prim_app_cb,[]}},
    │ │ │ │ +  {env, [{file, "/usr/local/log"}]}
    │ │ │ │ + ]}.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Synchronizing Processes during Startup │ │ │ │

    │ │ │ │

    The supervisor tree of an included application is started as part of the │ │ │ │ supervisor tree of the including application. If there is a need for │ │ │ │ synchronization between processes in the including and included applications, │ │ │ │ this can be achieved by using start phases.

    Start phases are defined by the start_phases key in the .app file as a list │ │ │ │ of tuples {Phase,PhaseArgs}, where Phase is an atom and PhaseArgs is a │ │ │ │ term.

    The value of the mod key of the including application must be set to │ │ │ │ {application_starter,[Module,StartArgs]}, where Module as usual is the │ │ │ │ application callback module. StartArgs is a term provided as argument to the │ │ │ │ -callback function Module:start/2:

    {application, prim_app,
    │ │ │ │ - [{description, "Tree application"},
    │ │ │ │ -  {vsn, "1"},
    │ │ │ │ -  {modules, [prim_app_cb, prim_app_sup, prim_app_server]},
    │ │ │ │ -  {registered, [prim_app_server]},
    │ │ │ │ -  {included_applications, [incl_app]},
    │ │ │ │ -  {start_phases, [{init,[]}, {go,[]}]},
    │ │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ │ -  {mod, {application_starter,[prim_app_cb,[]]}},
    │ │ │ │ -  {env, [{file, "/usr/local/log"}]}
    │ │ │ │ - ]}.
    │ │ │ │ +callback function Module:start/2:

    {application, prim_app,
    │ │ │ │ + [{description, "Tree application"},
    │ │ │ │ +  {vsn, "1"},
    │ │ │ │ +  {modules, [prim_app_cb, prim_app_sup, prim_app_server]},
    │ │ │ │ +  {registered, [prim_app_server]},
    │ │ │ │ +  {included_applications, [incl_app]},
    │ │ │ │ +  {start_phases, [{init,[]}, {go,[]}]},
    │ │ │ │ +  {applications, [kernel, stdlib, sasl]},
    │ │ │ │ +  {mod, {application_starter,[prim_app_cb,[]]}},
    │ │ │ │ +  {env, [{file, "/usr/local/log"}]}
    │ │ │ │ + ]}.
    │ │ │ │  
    │ │ │ │ -{application, incl_app,
    │ │ │ │ - [{description, "Included application"},
    │ │ │ │ -  {vsn, "1"},
    │ │ │ │ -  {modules, [incl_app_cb, incl_app_sup, incl_app_server]},
    │ │ │ │ -  {registered, []},
    │ │ │ │ -  {start_phases, [{go,[]}]},
    │ │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ │ -  {mod, {incl_app_cb,[]}}
    │ │ │ │ - ]}.

    When starting a primary application with included applications, the primary │ │ │ │ +{application, incl_app, │ │ │ │ + [{description, "Included application"}, │ │ │ │ + {vsn, "1"}, │ │ │ │ + {modules, [incl_app_cb, incl_app_sup, incl_app_server]}, │ │ │ │ + {registered, []}, │ │ │ │ + {start_phases, [{go,[]}]}, │ │ │ │ + {applications, [kernel, stdlib, sasl]}, │ │ │ │ + {mod, {incl_app_cb,[]}} │ │ │ │ + ]}.

    When starting a primary application with included applications, the primary │ │ │ │ application is started the normal way, that is:

    • The application controller creates an application master for the application
    • The application master calls Module:start(normal, StartArgs) to start the │ │ │ │ top supervisor.

    Then, for the primary application and each included application in top-down, │ │ │ │ left-to-right order, the application master calls │ │ │ │ Module:start_phase(Phase, Type, PhaseArgs) for each phase defined for the │ │ │ │ primary application, in that order. If a phase is not defined for an included │ │ │ │ application, the function is not called for this phase and application.

    The following requirements apply to the .app file for an included application:

    • The {mod, {Module,StartArgs}} option must be included. This option is used │ │ │ │ to find the callback module Module of the application. StartArgs is │ │ │ │ ignored, as Module:start/2 is called only for the primary application.
    • If the included application itself contains included applications, instead the │ │ │ │ {mod, {application_starter, [Module,StartArgs]}} option must be included.
    • The {start_phases, [{Phase,PhaseArgs}]} option must be included, and the set │ │ │ │ of specified phases must be a subset of the set of phases specified for the │ │ │ │ primary application.

    When starting prim_app as defined above, the application controller calls the │ │ │ │ following callback functions before application:start(prim_app) returns a │ │ │ │ -value:

    application:start(prim_app)
    │ │ │ │ - => prim_app_cb:start(normal, [])
    │ │ │ │ - => prim_app_cb:start_phase(init, normal, [])
    │ │ │ │ - => prim_app_cb:start_phase(go, normal, [])
    │ │ │ │ - => incl_app_cb:start_phase(go, normal, [])
    │ │ │ │ +value:

    application:start(prim_app)
    │ │ │ │ + => prim_app_cb:start(normal, [])
    │ │ │ │ + => prim_app_cb:start_phase(init, normal, [])
    │ │ │ │ + => prim_app_cb:start_phase(go, normal, [])
    │ │ │ │ + => incl_app_cb:start_phase(go, normal, [])
    │ │ │ │  ok
    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/gen_server_concepts.xhtml │ │ │ │ @@ -62,63 +62,63 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Example │ │ │ │ │ │ │ │

    An example of a simple server written in plain Erlang is provided in │ │ │ │ Overview. The server can be reimplemented using │ │ │ │ -gen_server, resulting in this callback module:

    -module(ch3).
    │ │ │ │ --behaviour(gen_server).
    │ │ │ │ +gen_server, resulting in this callback module:

    -module(ch3).
    │ │ │ │ +-behaviour(gen_server).
    │ │ │ │  
    │ │ │ │ --export([start_link/0]).
    │ │ │ │ --export([alloc/0, free/1]).
    │ │ │ │ --export([init/1, handle_call/3, handle_cast/2]).
    │ │ │ │ +-export([start_link/0]).
    │ │ │ │ +-export([alloc/0, free/1]).
    │ │ │ │ +-export([init/1, handle_call/3, handle_cast/2]).
    │ │ │ │  
    │ │ │ │ -start_link() ->
    │ │ │ │ -    gen_server:start_link({local, ch3}, ch3, [], []).
    │ │ │ │ +start_link() ->
    │ │ │ │ +    gen_server:start_link({local, ch3}, ch3, [], []).
    │ │ │ │  
    │ │ │ │ -alloc() ->
    │ │ │ │ -    gen_server:call(ch3, alloc).
    │ │ │ │ +alloc() ->
    │ │ │ │ +    gen_server:call(ch3, alloc).
    │ │ │ │  
    │ │ │ │ -free(Ch) ->
    │ │ │ │ -    gen_server:cast(ch3, {free, Ch}).
    │ │ │ │ +free(Ch) ->
    │ │ │ │ +    gen_server:cast(ch3, {free, Ch}).
    │ │ │ │  
    │ │ │ │ -init(_Args) ->
    │ │ │ │ -    {ok, channels()}.
    │ │ │ │ +init(_Args) ->
    │ │ │ │ +    {ok, channels()}.
    │ │ │ │  
    │ │ │ │ -handle_call(alloc, _From, Chs) ->
    │ │ │ │ -    {Ch, Chs2} = alloc(Chs),
    │ │ │ │ -    {reply, Ch, Chs2}.
    │ │ │ │ +handle_call(alloc, _From, Chs) ->
    │ │ │ │ +    {Ch, Chs2} = alloc(Chs),
    │ │ │ │ +    {reply, Ch, Chs2}.
    │ │ │ │  
    │ │ │ │ -handle_cast({free, Ch}, Chs) ->
    │ │ │ │ -    Chs2 = free(Ch, Chs),
    │ │ │ │ -    {noreply, Chs2}.

    The code is explained in the next sections.

    │ │ │ │ +handle_cast({free, Ch}, Chs) -> │ │ │ │ + Chs2 = free(Ch, Chs), │ │ │ │ + {noreply, Chs2}.

    The code is explained in the next sections.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Starting a Gen_Server │ │ │ │

    │ │ │ │

    In the example in the previous section, gen_server is started by calling │ │ │ │ -ch3:start_link():

    start_link() ->
    │ │ │ │ -    gen_server:start_link({local, ch3}, ch3, [], []) => {ok, Pid}

    start_link/0 calls function gen_server:start_link/4. This function │ │ │ │ +ch3:start_link():

    start_link() ->
    │ │ │ │ +    gen_server:start_link({local, ch3}, ch3, [], []) => {ok, Pid}

    start_link/0 calls function gen_server:start_link/4. This function │ │ │ │ spawns and links to a new process, a gen_server.

    • The first argument, {local, ch3}, specifies the name. │ │ │ │ The gen_server is then locally registered as ch3.

      If the name is omitted, the gen_server is not registered. Instead its pid │ │ │ │ must be used. The name can also be given as {global, Name}, in which case │ │ │ │ the gen_server is registered using global:register_name/2.

    • The second argument, ch3, is the name of the callback module, which is │ │ │ │ the module where the callback functions are located.

      The interface functions (start_link/0, alloc/0, and free/1) are located │ │ │ │ in the same module as the callback functions (init/1, handle_call/3, and │ │ │ │ handle_cast/2). It is usually good programming practice to have the code │ │ │ │ corresponding to one process contained in a single module.

    • The third argument, [], is a term that is passed as is to the callback │ │ │ │ function init. Here, init does not need any indata and ignores the │ │ │ │ argument.

    • The fourth argument, [], is a list of options. See gen_server │ │ │ │ for the available options.

    If name registration succeeds, the new gen_server process calls the callback │ │ │ │ function ch3:init([]). init is expected to return {ok, State}, where │ │ │ │ State is the internal state of the gen_server. In this case, the state is │ │ │ │ -the available channels.

    init(_Args) ->
    │ │ │ │ -    {ok, channels()}.

    gen_server:start_link/4 is synchronous. It does not return until the │ │ │ │ +the available channels.

    init(_Args) ->
    │ │ │ │ +    {ok, channels()}.

    gen_server:start_link/4 is synchronous. It does not return until the │ │ │ │ gen_server has been initialized and is ready to receive requests.

    gen_server:start_link/4 must be used if the gen_server is part of │ │ │ │ a supervision tree, meaning that it was started by a supervisor. There │ │ │ │ is another function, gen_server:start/4, to start a standalone │ │ │ │ gen_server that is not part of a supervision tree.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -126,32 +126,32 @@ │ │ │ │

    │ │ │ │

    The synchronous request alloc() is implemented using gen_server:call/2:

    alloc() ->
    │ │ │ │      gen_server:call(ch3, alloc).

    ch3 is the name of the gen_server and must agree with the name │ │ │ │ used to start it. alloc is the actual request.

    The request is made into a message and sent to the gen_server. │ │ │ │ When the request is received, the gen_server calls │ │ │ │ handle_call(Request, From, State), which is expected to return │ │ │ │ a tuple {reply,Reply,State1}. Reply is the reply that is to be sent back │ │ │ │ -to the client, and State1 is a new value for the state of the gen_server.

    handle_call(alloc, _From, Chs) ->
    │ │ │ │ -    {Ch, Chs2} = alloc(Chs),
    │ │ │ │ -    {reply, Ch, Chs2}.

    In this case, the reply is the allocated channel Ch and the new state is the │ │ │ │ +to the client, and State1 is a new value for the state of the gen_server.

    handle_call(alloc, _From, Chs) ->
    │ │ │ │ +    {Ch, Chs2} = alloc(Chs),
    │ │ │ │ +    {reply, Ch, Chs2}.

    In this case, the reply is the allocated channel Ch and the new state is the │ │ │ │ set of remaining available channels Chs2.

    Thus, the call ch3:alloc() returns the allocated channel Ch and the │ │ │ │ gen_server then waits for new requests, now with an updated list of │ │ │ │ available channels.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Asynchronous Requests - Cast │ │ │ │

    │ │ │ │ -

    The asynchronous request free(Ch) is implemented using gen_server:cast/2:

    free(Ch) ->
    │ │ │ │ -    gen_server:cast(ch3, {free, Ch}).

    ch3 is the name of the gen_server. {free, Ch} is the actual request.

    The request is made into a message and sent to the gen_server. │ │ │ │ +

    The asynchronous request free(Ch) is implemented using gen_server:cast/2:

    free(Ch) ->
    │ │ │ │ +    gen_server:cast(ch3, {free, Ch}).

    ch3 is the name of the gen_server. {free, Ch} is the actual request.

    The request is made into a message and sent to the gen_server. │ │ │ │ cast, and thus free, then returns ok.

    When the request is received, the gen_server calls │ │ │ │ handle_cast(Request, State), which is expected to return a tuple │ │ │ │ -{noreply,State1}. State1 is a new value for the state of the gen_server.

    handle_cast({free, Ch}, Chs) ->
    │ │ │ │ -    Chs2 = free(Ch, Chs),
    │ │ │ │ -    {noreply, Chs2}.

    In this case, the new state is the updated list of available channels Chs2. │ │ │ │ +{noreply,State1}. State1 is a new value for the state of the gen_server.

    handle_cast({free, Ch}, Chs) ->
    │ │ │ │ +    Chs2 = free(Ch, Chs),
    │ │ │ │ +    {noreply, Chs2}.

    In this case, the new state is the updated list of available channels Chs2. │ │ │ │ The gen_server is now ready for new requests.

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

    │ │ │ │

    │ │ │ │ @@ -162,65 +162,65 @@ │ │ │ │

    │ │ │ │

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

    If it is necessary to clean up before termination, the shutdown strategy │ │ │ │ must be a time-out value and the gen_server must be set to trap exit signals │ │ │ │ in function init. When ordered to shutdown, the gen_server then calls │ │ │ │ -the callback function terminate(shutdown, State):

    init(Args) ->
    │ │ │ │ +the callback function terminate(shutdown, State):

    init(Args) ->
    │ │ │ │      ...,
    │ │ │ │ -    process_flag(trap_exit, true),
    │ │ │ │ +    process_flag(trap_exit, true),
    │ │ │ │      ...,
    │ │ │ │ -    {ok, State}.
    │ │ │ │ +    {ok, State}.
    │ │ │ │  
    │ │ │ │  ...
    │ │ │ │  
    │ │ │ │ -terminate(shutdown, State) ->
    │ │ │ │ +terminate(shutdown, State) ->
    │ │ │ │      %% Code for cleaning up here
    │ │ │ │      ...
    │ │ │ │      ok.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Standalone Gen_Servers │ │ │ │

    │ │ │ │

    If the gen_server is not part of a supervision tree, a stop function │ │ │ │ can be useful, for example:

    ...
    │ │ │ │ -export([stop/0]).
    │ │ │ │ +export([stop/0]).
    │ │ │ │  ...
    │ │ │ │  
    │ │ │ │ -stop() ->
    │ │ │ │ -    gen_server:cast(ch3, stop).
    │ │ │ │ +stop() ->
    │ │ │ │ +    gen_server:cast(ch3, stop).
    │ │ │ │  ...
    │ │ │ │  
    │ │ │ │ -handle_cast(stop, State) ->
    │ │ │ │ -    {stop, normal, State};
    │ │ │ │ -handle_cast({free, Ch}, State) ->
    │ │ │ │ +handle_cast(stop, State) ->
    │ │ │ │ +    {stop, normal, State};
    │ │ │ │ +handle_cast({free, Ch}, State) ->
    │ │ │ │      ...
    │ │ │ │  
    │ │ │ │  ...
    │ │ │ │  
    │ │ │ │ -terminate(normal, State) ->
    │ │ │ │ +terminate(normal, State) ->
    │ │ │ │      ok.

    The callback function handling the stop request returns a tuple │ │ │ │ {stop,normal,State1}, where normal specifies that it is │ │ │ │ a normal termination and State1 is a new value for the state │ │ │ │ of the gen_server. This causes the gen_server to call │ │ │ │ terminate(normal, State1) and then it terminates gracefully.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Handling Other Messages │ │ │ │

    │ │ │ │

    If the gen_server is to be able to receive other messages than requests, │ │ │ │ the callback function handle_info(Info, State) must be implemented │ │ │ │ to handle them. Examples of other messages are exit messages, │ │ │ │ if the gen_server is linked to other processes than the supervisor │ │ │ │ -and it is trapping exit signals.

    handle_info({'EXIT', Pid, Reason}, State) ->
    │ │ │ │ +and it is trapping exit signals.

    handle_info({'EXIT', Pid, Reason}, State) ->
    │ │ │ │      %% Code to handle exits here.
    │ │ │ │      ...
    │ │ │ │ -    {noreply, State1}.

    The final function to implement is code_change/3:

    code_change(OldVsn, State, Extra) ->
    │ │ │ │ +    {noreply, State1}.

    The final function to implement is code_change/3:

    code_change(OldVsn, State, Extra) ->
    │ │ │ │      %% Code to convert state (and more) during code change.
    │ │ │ │      ...
    │ │ │ │ -    {ok, NewState}.
    │ │ │ │ +
    {ok, NewState}.
    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/funs.xhtml │ │ │ │ @@ -22,399 +22,399 @@ │ │ │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ map │ │ │ │

    │ │ │ │ -

    The following function, double, doubles every element in a list:

    double([H|T]) -> [2*H|double(T)];
    │ │ │ │ -double([])    -> [].

    Hence, the argument entered as input is doubled as follows:

    > double([1,2,3,4]).
    │ │ │ │ -[2,4,6,8]

    The following function, add_one, adds one to every element in a list:

    add_one([H|T]) -> [H+1|add_one(T)];
    │ │ │ │ -add_one([])    -> [].

    The functions double and add_one have a similar structure. This can be used │ │ │ │ -by writing a function map that expresses this similarity:

    map(F, [H|T]) -> [F(H)|map(F, T)];
    │ │ │ │ -map(F, [])    -> [].

    The functions double and add_one can now be expressed in terms of map as │ │ │ │ -follows:

    double(L)  -> map(fun(X) -> 2*X end, L).
    │ │ │ │ -add_one(L) -> map(fun(X) -> 1 + X end, L).

    map(F, List) is a function that takes a function F and a list L as │ │ │ │ +

    The following function, double, doubles every element in a list:

    double([H|T]) -> [2*H|double(T)];
    │ │ │ │ +double([])    -> [].

    Hence, the argument entered as input is doubled as follows:

    > double([1,2,3,4]).
    │ │ │ │ +[2,4,6,8]

    The following function, add_one, adds one to every element in a list:

    add_one([H|T]) -> [H+1|add_one(T)];
    │ │ │ │ +add_one([])    -> [].

    The functions double and add_one have a similar structure. This can be used │ │ │ │ +by writing a function map that expresses this similarity:

    map(F, [H|T]) -> [F(H)|map(F, T)];
    │ │ │ │ +map(F, [])    -> [].

    The functions double and add_one can now be expressed in terms of map as │ │ │ │ +follows:

    double(L)  -> map(fun(X) -> 2*X end, L).
    │ │ │ │ +add_one(L) -> map(fun(X) -> 1 + X end, L).

    map(F, List) is a function that takes a function F and a list L as │ │ │ │ arguments and returns a new list, obtained by applying F to each of the │ │ │ │ elements in L.

    The process of abstracting out the common features of a number of different │ │ │ │ programs is called procedural abstraction. Procedural abstraction can be used │ │ │ │ to write several different functions that have a similar structure, but differ │ │ │ │ in some minor detail. This is done as follows:

    1. Step 1. Write one function that represents the common features of these │ │ │ │ functions.
    2. Step 2. Parameterize the difference in terms of functions that are passed │ │ │ │ as arguments to the common function.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ foreach │ │ │ │

    │ │ │ │

    This section illustrates procedural abstraction. Initially, the following two │ │ │ │ -examples are written as conventional functions.

    This function prints all elements of a list onto a stream:

    print_list(Stream, [H|T]) ->
    │ │ │ │ -    io:format(Stream, "~p~n", [H]),
    │ │ │ │ -    print_list(Stream, T);
    │ │ │ │ -print_list(Stream, []) ->
    │ │ │ │ -    true.

    This function broadcasts a message to a list of processes:

    broadcast(Msg, [Pid|Pids]) ->
    │ │ │ │ +examples are written as conventional functions.

    This function prints all elements of a list onto a stream:

    print_list(Stream, [H|T]) ->
    │ │ │ │ +    io:format(Stream, "~p~n", [H]),
    │ │ │ │ +    print_list(Stream, T);
    │ │ │ │ +print_list(Stream, []) ->
    │ │ │ │ +    true.

    This function broadcasts a message to a list of processes:

    broadcast(Msg, [Pid|Pids]) ->
    │ │ │ │      Pid ! Msg,
    │ │ │ │ -    broadcast(Msg, Pids);
    │ │ │ │ -broadcast(_, []) ->
    │ │ │ │ +    broadcast(Msg, Pids);
    │ │ │ │ +broadcast(_, []) ->
    │ │ │ │      true.

    These two functions have a similar structure. They both iterate over a list and │ │ │ │ do something to each element in the list. The "something" is passed on as an │ │ │ │ -extra argument to the function that does this.

    The function foreach expresses this similarity:

    foreach(F, [H|T]) ->
    │ │ │ │ -    F(H),
    │ │ │ │ -    foreach(F, T);
    │ │ │ │ -foreach(F, []) ->
    │ │ │ │ -    ok.

    Using the function foreach, the function print_list becomes:

    foreach(fun(H) -> io:format(S, "~p~n",[H]) end, L)

    Using the function foreach, the function broadcast becomes:

    foreach(fun(Pid) -> Pid ! M end, L)

    foreach is evaluated for its side-effect and not its value. foreach(Fun ,L) │ │ │ │ +extra argument to the function that does this.

    The function foreach expresses this similarity:

    foreach(F, [H|T]) ->
    │ │ │ │ +    F(H),
    │ │ │ │ +    foreach(F, T);
    │ │ │ │ +foreach(F, []) ->
    │ │ │ │ +    ok.

    Using the function foreach, the function print_list becomes:

    foreach(fun(H) -> io:format(S, "~p~n",[H]) end, L)

    Using the function foreach, the function broadcast becomes:

    foreach(fun(Pid) -> Pid ! M end, L)

    foreach is evaluated for its side-effect and not its value. foreach(Fun ,L) │ │ │ │ calls Fun(X) for each element X in L and the processing occurs in the │ │ │ │ order that the elements were defined in L. map does not define the order in │ │ │ │ which its elements are processed.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Syntax of Funs │ │ │ │

    │ │ │ │

    Funs are written with the following syntax (see │ │ │ │ -Fun Expressions for full description):

    F = fun (Arg1, Arg2, ... ArgN) ->
    │ │ │ │ +Fun Expressions for full description):

    F = fun (Arg1, Arg2, ... ArgN) ->
    │ │ │ │          ...
    │ │ │ │      end

    This creates an anonymous function of N arguments and binds it to the variable │ │ │ │ F.

    Another function, FunctionName, written in the same module, can be passed as │ │ │ │ an argument, using the following syntax:

    F = fun FunctionName/Arity

    With this form of function reference, the function that is referred to does not │ │ │ │ need to be exported from the module.

    It is also possible to refer to a function defined in a different module, with │ │ │ │ -the following syntax:

    F = fun Module:FunctionName/Arity

    In this case, the function must be exported from the module in question.

    The following program illustrates the different ways of creating funs:

    -module(fun_test).
    │ │ │ │ --export([t1/0, t2/0]).
    │ │ │ │ --import(lists, [map/2]).
    │ │ │ │ +the following syntax:

    F = fun Module:FunctionName/Arity

    In this case, the function must be exported from the module in question.

    The following program illustrates the different ways of creating funs:

    -module(fun_test).
    │ │ │ │ +-export([t1/0, t2/0]).
    │ │ │ │ +-import(lists, [map/2]).
    │ │ │ │  
    │ │ │ │ -t1() -> map(fun(X) -> 2 * X end, [1,2,3,4,5]).
    │ │ │ │ +t1() -> map(fun(X) -> 2 * X end, [1,2,3,4,5]).
    │ │ │ │  
    │ │ │ │ -t2() -> map(fun double/1, [1,2,3,4,5]).
    │ │ │ │ +t2() -> map(fun double/1, [1,2,3,4,5]).
    │ │ │ │  
    │ │ │ │ -double(X) -> X * 2.

    The fun F can be evaluated with the following syntax:

    F(Arg1, Arg2, ..., Argn)

    To check whether a term is a fun, use the test │ │ │ │ -is_function/1 in a guard.

    Example:

    f(F, Args) when is_function(F) ->
    │ │ │ │ -   apply(F, Args);
    │ │ │ │ -f(N, _) when is_integer(N) ->
    │ │ │ │ +double(X) -> X * 2.

    The fun F can be evaluated with the following syntax:

    F(Arg1, Arg2, ..., Argn)

    To check whether a term is a fun, use the test │ │ │ │ +is_function/1 in a guard.

    Example:

    f(F, Args) when is_function(F) ->
    │ │ │ │ +   apply(F, Args);
    │ │ │ │ +f(N, _) when is_integer(N) ->
    │ │ │ │     N.

    Funs are a distinct type. The BIFs erlang:fun_info/1,2 can be used to retrieve │ │ │ │ information about a fun, and the BIF erlang:fun_to_list/1 returns a textual │ │ │ │ representation of a fun. The check_process_code/2 │ │ │ │ BIF returns true if the process contains funs that depend on the old version │ │ │ │ of a module.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Variable Bindings Within a Fun │ │ │ │

    │ │ │ │

    The scope rules for variables that occur in funs are as follows:

    • All variables that occur in the head of a fun are assumed to be "fresh" │ │ │ │ variables.
    • Variables that are defined before the fun, and that occur in function calls or │ │ │ │ -guard tests within the fun, have the values they had outside the fun.
    • Variables cannot be exported from a fun.

    The following examples illustrate these rules:

    print_list(File, List) ->
    │ │ │ │ -    {ok, Stream} = file:open(File, write),
    │ │ │ │ -    foreach(fun(X) -> io:format(Stream,"~p~n",[X]) end, List),
    │ │ │ │ -    file:close(Stream).

    Here, the variable X, defined in the head of the fun, is a new variable. The │ │ │ │ +guard tests within the fun, have the values they had outside the fun.

  • Variables cannot be exported from a fun.

The following examples illustrate these rules:

print_list(File, List) ->
│ │ │ │ +    {ok, Stream} = file:open(File, write),
│ │ │ │ +    foreach(fun(X) -> io:format(Stream,"~p~n",[X]) end, List),
│ │ │ │ +    file:close(Stream).

Here, the variable X, defined in the head of the fun, is a new variable. The │ │ │ │ variable Stream, which is used within the fun, gets its value from the │ │ │ │ file:open line.

As any variable that occurs in the head of a fun is considered a new variable, │ │ │ │ -it is equally valid to write as follows:

print_list(File, List) ->
│ │ │ │ -    {ok, Stream} = file:open(File, write),
│ │ │ │ -    foreach(fun(File) ->
│ │ │ │ -                io:format(Stream,"~p~n",[File])
│ │ │ │ -            end, List),
│ │ │ │ -    file:close(Stream).

Here, File is used as the new variable instead of X. This is not so wise │ │ │ │ +it is equally valid to write as follows:

print_list(File, List) ->
│ │ │ │ +    {ok, Stream} = file:open(File, write),
│ │ │ │ +    foreach(fun(File) ->
│ │ │ │ +                io:format(Stream,"~p~n",[File])
│ │ │ │ +            end, List),
│ │ │ │ +    file:close(Stream).

Here, File is used as the new variable instead of X. This is not so wise │ │ │ │ because code in the fun body cannot refer to the variable File, which is │ │ │ │ defined outside of the fun. Compiling this example gives the following │ │ │ │ diagnostic:

./FileName.erl:Line: Warning: variable 'File'
│ │ │ │        shadowed in 'fun'

This indicates that the variable File, which is defined inside the fun, │ │ │ │ collides with the variable File, which is defined outside the fun.

The rules for importing variables into a fun has the consequence that certain │ │ │ │ pattern matching operations must be moved into guard expressions and cannot be │ │ │ │ written in the head of the fun. For example, you might write the following code │ │ │ │ if you intend the first clause of F to be evaluated when the value of its │ │ │ │ -argument is Y:

f(...) ->
│ │ │ │ +argument is Y:

f(...) ->
│ │ │ │      Y = ...
│ │ │ │ -    map(fun(X) when X == Y ->
│ │ │ │ +    map(fun(X) when X == Y ->
│ │ │ │               ;
│ │ │ │ -           (_) ->
│ │ │ │ +           (_) ->
│ │ │ │               ...
│ │ │ │ -        end, ...)
│ │ │ │ -    ...

instead of writing the following code:

f(...) ->
│ │ │ │ +        end, ...)
│ │ │ │ +    ...

instead of writing the following code:

f(...) ->
│ │ │ │      Y = ...
│ │ │ │ -    map(fun(Y) ->
│ │ │ │ +    map(fun(Y) ->
│ │ │ │               ;
│ │ │ │ -           (_) ->
│ │ │ │ +           (_) ->
│ │ │ │               ...
│ │ │ │ -        end, ...)
│ │ │ │ +        end, ...)
│ │ │ │      ...

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Funs and Module Lists │ │ │ │

│ │ │ │

The following examples show a dialogue with the Erlang shell. All the higher │ │ │ │ order functions discussed are exported from the module lists.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ map │ │ │ │

│ │ │ │ -

lists:map/2 takes a function of one argument and a list of terms:

map(F, [H|T]) -> [F(H)|map(F, T)];
│ │ │ │ -map(F, [])    -> [].

It returns the list obtained by applying the function to every argument in the │ │ │ │ +

lists:map/2 takes a function of one argument and a list of terms:

map(F, [H|T]) -> [F(H)|map(F, T)];
│ │ │ │ +map(F, [])    -> [].

It returns the list obtained by applying the function to every argument in the │ │ │ │ list.

When a new fun is defined in the shell, the value of the fun is printed as │ │ │ │ -Fun#<erl_eval>:

> Double = fun(X) -> 2 * X end.
│ │ │ │ +Fun#<erl_eval>:

> Double = fun(X) -> 2 * X end.
│ │ │ │  #Fun<erl_eval.6.72228031>
│ │ │ │ -> lists:map(Double, [1,2,3,4,5]).
│ │ │ │ -[2,4,6,8,10]

│ │ │ │ +> lists:map(Double, [1,2,3,4,5]). │ │ │ │ +[2,4,6,8,10]

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ any │ │ │ │

│ │ │ │ -

lists:any/2 takes a predicate P of one argument and a list of terms:

any(Pred, [H|T]) ->
│ │ │ │ -    case Pred(H) of
│ │ │ │ +

lists:any/2 takes a predicate P of one argument and a list of terms:

any(Pred, [H|T]) ->
│ │ │ │ +    case Pred(H) of
│ │ │ │          true  ->  true;
│ │ │ │ -        false ->  any(Pred, T)
│ │ │ │ +        false ->  any(Pred, T)
│ │ │ │      end;
│ │ │ │ -any(Pred, []) ->
│ │ │ │ +any(Pred, []) ->
│ │ │ │      false.

A predicate is a function that returns true or false. any is true if │ │ │ │ there is a term X in the list such that P(X) is true.

A predicate Big(X) is defined, which is true if its argument is greater that │ │ │ │ -10:

> Big =  fun(X) -> if X > 10 -> true; true -> false end end.
│ │ │ │ +10:

> Big =  fun(X) -> if X > 10 -> true; true -> false end end.
│ │ │ │  #Fun<erl_eval.6.72228031>
│ │ │ │ -> lists:any(Big, [1,2,3,4]).
│ │ │ │ +> lists:any(Big, [1,2,3,4]).
│ │ │ │  false
│ │ │ │ -> lists:any(Big, [1,2,3,12,5]).
│ │ │ │ +> lists:any(Big, [1,2,3,12,5]).
│ │ │ │  true

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ all │ │ │ │

│ │ │ │ -

lists:all/2 has the same arguments as any:

all(Pred, [H|T]) ->
│ │ │ │ -    case Pred(H) of
│ │ │ │ -        true  ->  all(Pred, T);
│ │ │ │ +

lists:all/2 has the same arguments as any:

all(Pred, [H|T]) ->
│ │ │ │ +    case Pred(H) of
│ │ │ │ +        true  ->  all(Pred, T);
│ │ │ │          false ->  false
│ │ │ │      end;
│ │ │ │ -all(Pred, []) ->
│ │ │ │ -    true.

It is true if the predicate applied to all elements in the list is true.

> lists:all(Big, [1,2,3,4,12,6]).
│ │ │ │ +all(Pred, []) ->
│ │ │ │ +    true.

It is true if the predicate applied to all elements in the list is true.

> lists:all(Big, [1,2,3,4,12,6]).
│ │ │ │  false
│ │ │ │ -> lists:all(Big, [12,13,14,15]).
│ │ │ │ +> lists:all(Big, [12,13,14,15]).
│ │ │ │  true

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ foreach │ │ │ │

│ │ │ │ -

lists:foreach/2 takes a function of one argument and a list of terms:

foreach(F, [H|T]) ->
│ │ │ │ -    F(H),
│ │ │ │ -    foreach(F, T);
│ │ │ │ -foreach(F, []) ->
│ │ │ │ +

lists:foreach/2 takes a function of one argument and a list of terms:

foreach(F, [H|T]) ->
│ │ │ │ +    F(H),
│ │ │ │ +    foreach(F, T);
│ │ │ │ +foreach(F, []) ->
│ │ │ │      ok.

The function is applied to each argument in the list. foreach returns ok. It │ │ │ │ -is only used for its side-effect:

> lists:foreach(fun(X) -> io:format("~w~n",[X]) end, [1,2,3,4]).
│ │ │ │ +is only used for its side-effect:

> lists:foreach(fun(X) -> io:format("~w~n",[X]) end, [1,2,3,4]).
│ │ │ │  1
│ │ │ │  2
│ │ │ │  3
│ │ │ │  4
│ │ │ │  ok

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ foldl │ │ │ │

│ │ │ │ -

lists:foldl/3 takes a function of two arguments, an accumulator and a list:

foldl(F, Accu, [Hd|Tail]) ->
│ │ │ │ -    foldl(F, F(Hd, Accu), Tail);
│ │ │ │ -foldl(F, Accu, []) -> Accu.

The function is called with two arguments. The first argument is the successive │ │ │ │ +

lists:foldl/3 takes a function of two arguments, an accumulator and a list:

foldl(F, Accu, [Hd|Tail]) ->
│ │ │ │ +    foldl(F, F(Hd, Accu), Tail);
│ │ │ │ +foldl(F, Accu, []) -> Accu.

The function is called with two arguments. The first argument is the successive │ │ │ │ elements in the list. The second argument is the accumulator. The function must │ │ │ │ return a new accumulator, which is used the next time the function is called.

If you have a list of lists L = ["I","like","Erlang"], then you can sum the │ │ │ │ -lengths of all the strings in L as follows:

> L = ["I","like","Erlang"].
│ │ │ │ -["I","like","Erlang"]
│ │ │ │ -10> lists:foldl(fun(X, Sum) -> length(X) + Sum end, 0, L).
│ │ │ │ -11

lists:foldl/3 works like a while loop in an imperative language:

L =  ["I","like","Erlang"],
│ │ │ │ +lengths of all the strings in L as follows:

> L = ["I","like","Erlang"].
│ │ │ │ +["I","like","Erlang"]
│ │ │ │ +10> lists:foldl(fun(X, Sum) -> length(X) + Sum end, 0, L).
│ │ │ │ +11

lists:foldl/3 works like a while loop in an imperative language:

L =  ["I","like","Erlang"],
│ │ │ │  Sum = 0,
│ │ │ │ -while( L != []){
│ │ │ │ -    Sum += length(head(L)),
│ │ │ │ -    L = tail(L)
│ │ │ │ +while( L != []){
│ │ │ │ +    Sum += length(head(L)),
│ │ │ │ +    L = tail(L)
│ │ │ │  end

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ mapfoldl │ │ │ │

│ │ │ │ -

lists:mapfoldl/3 simultaneously maps and folds over a list:

mapfoldl(F, Accu0, [Hd|Tail]) ->
│ │ │ │ -    {R,Accu1} = F(Hd, Accu0),
│ │ │ │ -    {Rs,Accu2} = mapfoldl(F, Accu1, Tail),
│ │ │ │ -    {[R|Rs], Accu2};
│ │ │ │ -mapfoldl(F, Accu, []) -> {[], Accu}.

The following example shows how to change all letters in L to upper case and │ │ │ │ -then count them.

First the change to upper case:

> Upcase =  fun(X) when $a =< X,  X =< $z -> X + $A - $a;
│ │ │ │ -(X) -> X
│ │ │ │ +

lists:mapfoldl/3 simultaneously maps and folds over a list:

mapfoldl(F, Accu0, [Hd|Tail]) ->
│ │ │ │ +    {R,Accu1} = F(Hd, Accu0),
│ │ │ │ +    {Rs,Accu2} = mapfoldl(F, Accu1, Tail),
│ │ │ │ +    {[R|Rs], Accu2};
│ │ │ │ +mapfoldl(F, Accu, []) -> {[], Accu}.

The following example shows how to change all letters in L to upper case and │ │ │ │ +then count them.

First the change to upper case:

> Upcase =  fun(X) when $a =< X,  X =< $z -> X + $A - $a;
│ │ │ │ +(X) -> X
│ │ │ │  end.
│ │ │ │  #Fun<erl_eval.6.72228031>
│ │ │ │  > Upcase_word =
│ │ │ │ -fun(X) ->
│ │ │ │ -lists:map(Upcase, X)
│ │ │ │ +fun(X) ->
│ │ │ │ +lists:map(Upcase, X)
│ │ │ │  end.
│ │ │ │  #Fun<erl_eval.6.72228031>
│ │ │ │ -> Upcase_word("Erlang").
│ │ │ │ +> Upcase_word("Erlang").
│ │ │ │  "ERLANG"
│ │ │ │ -> lists:map(Upcase_word, L).
│ │ │ │ -["I","LIKE","ERLANG"]

Now, the fold and the map can be done at the same time:

> lists:mapfoldl(fun(Word, Sum) ->
│ │ │ │ -{Upcase_word(Word), Sum + length(Word)}
│ │ │ │ -end, 0, L).
│ │ │ │ -{["I","LIKE","ERLANG"],11}

│ │ │ │ +> lists:map(Upcase_word, L). │ │ │ │ +["I","LIKE","ERLANG"]

Now, the fold and the map can be done at the same time:

> lists:mapfoldl(fun(Word, Sum) ->
│ │ │ │ +{Upcase_word(Word), Sum + length(Word)}
│ │ │ │ +end, 0, L).
│ │ │ │ +{["I","LIKE","ERLANG"],11}

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ filter │ │ │ │

│ │ │ │

lists:filter/2 takes a predicate of one argument and a list and returns all elements │ │ │ │ -in the list that satisfy the predicate:

filter(F, [H|T]) ->
│ │ │ │ -    case F(H) of
│ │ │ │ -        true  -> [H|filter(F, T)];
│ │ │ │ -        false -> filter(F, T)
│ │ │ │ +in the list that satisfy the predicate:

filter(F, [H|T]) ->
│ │ │ │ +    case F(H) of
│ │ │ │ +        true  -> [H|filter(F, T)];
│ │ │ │ +        false -> filter(F, T)
│ │ │ │      end;
│ │ │ │ -filter(F, []) -> [].
> lists:filter(Big, [500,12,2,45,6,7]).
│ │ │ │ -[500,12,45]

Combining maps and filters enables writing of very succinct code. For example, │ │ │ │ +filter(F, []) -> [].

> lists:filter(Big, [500,12,2,45,6,7]).
│ │ │ │ +[500,12,45]

Combining maps and filters enables writing of very succinct code. For example, │ │ │ │ to define a set difference function diff(L1, L2) to be the difference between │ │ │ │ -the lists L1 and L2, the code can be written as follows:

diff(L1, L2) ->
│ │ │ │ -    filter(fun(X) -> not member(X, L2) end, L1).

This gives the list of all elements in L1 that are not contained in L2.

The AND intersection of the list L1 and L2 is also easily defined:

intersection(L1,L2) -> filter(fun(X) -> member(X,L1) end, L2).

│ │ │ │ +the lists L1 and L2, the code can be written as follows:

diff(L1, L2) ->
│ │ │ │ +    filter(fun(X) -> not member(X, L2) end, L1).

This gives the list of all elements in L1 that are not contained in L2.

The AND intersection of the list L1 and L2 is also easily defined:

intersection(L1,L2) -> filter(fun(X) -> member(X,L1) end, L2).

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ takewhile │ │ │ │

│ │ │ │

lists:takewhile/2 takes elements X from a list L as long as the predicate │ │ │ │ -P(X) is true:

takewhile(Pred, [H|T]) ->
│ │ │ │ -    case Pred(H) of
│ │ │ │ -        true  -> [H|takewhile(Pred, T)];
│ │ │ │ -        false -> []
│ │ │ │ +P(X) is true:

takewhile(Pred, [H|T]) ->
│ │ │ │ +    case Pred(H) of
│ │ │ │ +        true  -> [H|takewhile(Pred, T)];
│ │ │ │ +        false -> []
│ │ │ │      end;
│ │ │ │ -takewhile(Pred, []) ->
│ │ │ │ -    [].
> lists:takewhile(Big, [200,500,45,5,3,45,6]).
│ │ │ │ -[200,500,45]

│ │ │ │ +takewhile(Pred, []) -> │ │ │ │ + [].

> lists:takewhile(Big, [200,500,45,5,3,45,6]).
│ │ │ │ +[200,500,45]

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ dropwhile │ │ │ │

│ │ │ │ -

lists:dropwhile/2 is the complement of takewhile:

dropwhile(Pred, [H|T]) ->
│ │ │ │ -    case Pred(H) of
│ │ │ │ -        true  -> dropwhile(Pred, T);
│ │ │ │ -        false -> [H|T]
│ │ │ │ +

lists:dropwhile/2 is the complement of takewhile:

dropwhile(Pred, [H|T]) ->
│ │ │ │ +    case Pred(H) of
│ │ │ │ +        true  -> dropwhile(Pred, T);
│ │ │ │ +        false -> [H|T]
│ │ │ │      end;
│ │ │ │ -dropwhile(Pred, []) ->
│ │ │ │ -    [].
> lists:dropwhile(Big, [200,500,45,5,3,45,6]).
│ │ │ │ -[5,3,45,6]

│ │ │ │ +dropwhile(Pred, []) -> │ │ │ │ + [].

> lists:dropwhile(Big, [200,500,45,5,3,45,6]).
│ │ │ │ +[5,3,45,6]

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ splitwith │ │ │ │

│ │ │ │

lists:splitwith/2 splits the list L into the two sublists {L1, L2}, where │ │ │ │ -L = takewhile(P, L) and L2 = dropwhile(P, L):

splitwith(Pred, L) ->
│ │ │ │ -    splitwith(Pred, L, []).
│ │ │ │ +L = takewhile(P, L) and L2 = dropwhile(P, L):

splitwith(Pred, L) ->
│ │ │ │ +    splitwith(Pred, L, []).
│ │ │ │  
│ │ │ │ -splitwith(Pred, [H|T], L) ->
│ │ │ │ -    case Pred(H) of
│ │ │ │ -        true  -> splitwith(Pred, T, [H|L]);
│ │ │ │ -        false -> {reverse(L), [H|T]}
│ │ │ │ +splitwith(Pred, [H|T], L) ->
│ │ │ │ +    case Pred(H) of
│ │ │ │ +        true  -> splitwith(Pred, T, [H|L]);
│ │ │ │ +        false -> {reverse(L), [H|T]}
│ │ │ │      end;
│ │ │ │ -splitwith(Pred, [], L) ->
│ │ │ │ -    {reverse(L), []}.
> lists:splitwith(Big, [200,500,45,5,3,45,6]).
│ │ │ │ -{[200,500,45],[5,3,45,6]}

│ │ │ │ +splitwith(Pred, [], L) -> │ │ │ │ + {reverse(L), []}.

> lists:splitwith(Big, [200,500,45,5,3,45,6]).
│ │ │ │ +{[200,500,45],[5,3,45,6]}

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Funs Returning Funs │ │ │ │

│ │ │ │

So far, only functions that take funs as arguments have been described. More │ │ │ │ powerful functions, that themselves return funs, can also be written. The │ │ │ │ following examples illustrate these type of functions.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Simple Higher Order Functions │ │ │ │

│ │ │ │

Adder(X) is a function that given X, returns a new function G such that │ │ │ │ -G(K) returns K + X:

> Adder = fun(X) -> fun(Y) -> X + Y end end.
│ │ │ │ +G(K) returns K + X:

> Adder = fun(X) -> fun(Y) -> X + Y end end.
│ │ │ │  #Fun<erl_eval.6.72228031>
│ │ │ │ -> Add6 = Adder(6).
│ │ │ │ +> Add6 = Adder(6).
│ │ │ │  #Fun<erl_eval.6.72228031>
│ │ │ │ -> Add6(10).
│ │ │ │ +> Add6(10).
│ │ │ │  16

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Infinite Lists │ │ │ │

│ │ │ │ -

The idea is to write something like:

-module(lazy).
│ │ │ │ --export([ints_from/1]).
│ │ │ │ -ints_from(N) ->
│ │ │ │ -    fun() ->
│ │ │ │ -            [N|ints_from(N+1)]
│ │ │ │ -    end.

Then proceed as follows:

> XX = lazy:ints_from(1).
│ │ │ │ +

The idea is to write something like:

-module(lazy).
│ │ │ │ +-export([ints_from/1]).
│ │ │ │ +ints_from(N) ->
│ │ │ │ +    fun() ->
│ │ │ │ +            [N|ints_from(N+1)]
│ │ │ │ +    end.

Then proceed as follows:

> XX = lazy:ints_from(1).
│ │ │ │  #Fun<lazy.0.29874839>
│ │ │ │ -> XX().
│ │ │ │ -[1|#Fun<lazy.0.29874839>]
│ │ │ │ -> hd(XX()).
│ │ │ │ +> XX().
│ │ │ │ +[1|#Fun<lazy.0.29874839>]
│ │ │ │ +> hd(XX()).
│ │ │ │  1
│ │ │ │ -> Y = tl(XX()).
│ │ │ │ +> Y = tl(XX()).
│ │ │ │  #Fun<lazy.0.29874839>
│ │ │ │ -> hd(Y()).
│ │ │ │ +> hd(Y()).
│ │ │ │  2

And so on. This is an example of "lazy embedding".

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Parsing │ │ │ │

│ │ │ │ -

The following examples show parsers of the following type:

Parser(Toks) -> {ok, Tree, Toks1} | fail

Toks is the list of tokens to be parsed. A successful parse returns │ │ │ │ +

The following examples show parsers of the following type:

Parser(Toks) -> {ok, Tree, Toks1} | fail

Toks is the list of tokens to be parsed. A successful parse returns │ │ │ │ {ok, Tree, Toks1}.

  • Tree is a parse tree.
  • Toks1 is a tail of Tree that contains symbols encountered after the │ │ │ │ structure that was correctly parsed.

An unsuccessful parse returns fail.

The following example illustrates a simple, functional parser that parses the │ │ │ │ grammar:

(a | b) & (c | d)

The following code defines a function pconst(X) in the module funparse, │ │ │ │ -which returns a fun that parses a list of tokens:

pconst(X) ->
│ │ │ │ -    fun (T) ->
│ │ │ │ +which returns a fun that parses a list of tokens:

pconst(X) ->
│ │ │ │ +    fun (T) ->
│ │ │ │         case T of
│ │ │ │ -           [X|T1] -> {ok, {const, X}, T1};
│ │ │ │ +           [X|T1] -> {ok, {const, X}, T1};
│ │ │ │             _      -> fail
│ │ │ │         end
│ │ │ │ -    end.

This function can be used as follows:

> P1 = funparse:pconst(a).
│ │ │ │ +    end.

This function can be used as follows:

> P1 = funparse:pconst(a).
│ │ │ │  #Fun<funparse.0.22674075>
│ │ │ │ -> P1([a,b,c]).
│ │ │ │ -{ok,{const,a},[b,c]}
│ │ │ │ -> P1([x,y,z]).
│ │ │ │ +> P1([a,b,c]).
│ │ │ │ +{ok,{const,a},[b,c]}
│ │ │ │ +> P1([x,y,z]).
│ │ │ │  fail

Next, the two higher order functions pand and por are defined. They combine │ │ │ │ -primitive parsers to produce more complex parsers.

First pand:

pand(P1, P2) ->
│ │ │ │ -    fun (T) ->
│ │ │ │ -        case P1(T) of
│ │ │ │ -            {ok, R1, T1} ->
│ │ │ │ -                case P2(T1) of
│ │ │ │ -                    {ok, R2, T2} ->
│ │ │ │ -                        {ok, {'and', R1, R2}};
│ │ │ │ +primitive parsers to produce more complex parsers.

First pand:

pand(P1, P2) ->
│ │ │ │ +    fun (T) ->
│ │ │ │ +        case P1(T) of
│ │ │ │ +            {ok, R1, T1} ->
│ │ │ │ +                case P2(T1) of
│ │ │ │ +                    {ok, R2, T2} ->
│ │ │ │ +                        {ok, {'and', R1, R2}};
│ │ │ │                      fail ->
│ │ │ │                          fail
│ │ │ │                  end;
│ │ │ │              fail ->
│ │ │ │                  fail
│ │ │ │          end
│ │ │ │      end.

Given a parser P1 for grammar G1, and a parser P2 for grammar G2, │ │ │ │ pand(P1, P2) returns a parser for the grammar, which consists of sequences of │ │ │ │ tokens that satisfy G1, followed by sequences of tokens that satisfy G2.

por(P1, P2) returns a parser for the language described by the grammar G1 or │ │ │ │ -G2:

por(P1, P2) ->
│ │ │ │ -    fun (T) ->
│ │ │ │ -        case P1(T) of
│ │ │ │ -            {ok, R, T1} ->
│ │ │ │ -                {ok, {'or',1,R}, T1};
│ │ │ │ +G2:

por(P1, P2) ->
│ │ │ │ +    fun (T) ->
│ │ │ │ +        case P1(T) of
│ │ │ │ +            {ok, R, T1} ->
│ │ │ │ +                {ok, {'or',1,R}, T1};
│ │ │ │              fail ->
│ │ │ │ -                case P2(T) of
│ │ │ │ -                    {ok, R1, T1} ->
│ │ │ │ -                        {ok, {'or',2,R1}, T1};
│ │ │ │ +                case P2(T) of
│ │ │ │ +                    {ok, R1, T1} ->
│ │ │ │ +                        {ok, {'or',2,R1}, T1};
│ │ │ │                      fail ->
│ │ │ │                          fail
│ │ │ │                  end
│ │ │ │          end
│ │ │ │      end.

The original problem was to parse the grammar (a | b) & (c | d). The following │ │ │ │ -code addresses this problem:

grammar() ->
│ │ │ │ -    pand(
│ │ │ │ -         por(pconst(a), pconst(b)),
│ │ │ │ -         por(pconst(c), pconst(d))).

The following code adds a parser interface to the grammar:

parse(List) ->
│ │ │ │ -    (grammar())(List).

The parser can be tested as follows:

> funparse:parse([a,c]).
│ │ │ │ -{ok,{'and',{'or',1,{const,a}},{'or',1,{const,c}}}}
│ │ │ │ -> funparse:parse([a,d]).
│ │ │ │ -{ok,{'and',{'or',1,{const,a}},{'or',2,{const,d}}}}
│ │ │ │ -> funparse:parse([b,c]).
│ │ │ │ -{ok,{'and',{'or',2,{const,b}},{'or',1,{const,c}}}}
│ │ │ │ -> funparse:parse([b,d]).
│ │ │ │ -{ok,{'and',{'or',2,{const,b}},{'or',2,{const,d}}}}
│ │ │ │ -> funparse:parse([a,b]).
│ │ │ │ +code addresses this problem:

grammar() ->
│ │ │ │ +    pand(
│ │ │ │ +         por(pconst(a), pconst(b)),
│ │ │ │ +         por(pconst(c), pconst(d))).

The following code adds a parser interface to the grammar:

parse(List) ->
│ │ │ │ +    (grammar())(List).

The parser can be tested as follows:

> funparse:parse([a,c]).
│ │ │ │ +{ok,{'and',{'or',1,{const,a}},{'or',1,{const,c}}}}
│ │ │ │ +> funparse:parse([a,d]).
│ │ │ │ +{ok,{'and',{'or',1,{const,a}},{'or',2,{const,d}}}}
│ │ │ │ +> funparse:parse([b,c]).
│ │ │ │ +{ok,{'and',{'or',2,{const,b}},{'or',1,{const,c}}}}
│ │ │ │ +> funparse:parse([b,d]).
│ │ │ │ +{ok,{'and',{'or',2,{const,b}},{'or',2,{const,d}}}}
│ │ │ │ +> funparse:parse([a,b]).
│ │ │ │  fail
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/expressions.xhtml │ │ │ │ @@ -56,81 +56,81 @@ │ │ │ │
Phone_number │ │ │ │ _ │ │ │ │ _Height │ │ │ │ name@node

Variables are bound to values using pattern matching. Erlang uses │ │ │ │ single assignment, that is, a variable can only be bound once.

The anonymous variable is denoted by underscore (_) and can be used when a │ │ │ │ variable is required but its value can be ignored.

Example:

[H|_] = [1,2,3]

Variables starting with underscore (_), for example, _Height, are normal │ │ │ │ variables, not anonymous. However, they are ignored by the compiler in the sense │ │ │ │ -that they do not generate warnings.

Example:

The following code:

member(_, []) ->
│ │ │ │ -    [].

can be rewritten to be more readable:

member(Elem, []) ->
│ │ │ │ -    [].

This causes a warning for an unused variable, Elem. To avoid the warning, │ │ │ │ -the code can be rewritten to:

member(_Elem, []) ->
│ │ │ │ -    [].

Notice that since variables starting with an underscore are not anonymous, the │ │ │ │ -following example matches:

{_,_} = {1,2}

But this example fails:

{_N,_N} = {1,2}

The scope for a variable is its function clause. Variables bound in a branch of │ │ │ │ +that they do not generate warnings.

Example:

The following code:

member(_, []) ->
│ │ │ │ +    [].

can be rewritten to be more readable:

member(Elem, []) ->
│ │ │ │ +    [].

This causes a warning for an unused variable, Elem. To avoid the warning, │ │ │ │ +the code can be rewritten to:

member(_Elem, []) ->
│ │ │ │ +    [].

Notice that since variables starting with an underscore are not anonymous, the │ │ │ │ +following example matches:

{_,_} = {1,2}

But this example fails:

{_N,_N} = {1,2}

The scope for a variable is its function clause. Variables bound in a branch of │ │ │ │ an if, case, or receive expression must be bound in all branches to have a │ │ │ │ value outside the expression. Otherwise they are regarded as unsafe outside │ │ │ │ the expression.

For the try expression variable scoping is limited so that variables bound in │ │ │ │ the expression are always unsafe outside the expression.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Patterns │ │ │ │

│ │ │ │

A pattern has the same structure as a term but can contain unbound variables.

Example:

Name1
│ │ │ │ -[H|T]
│ │ │ │ -{error,Reason}

Patterns are allowed in clause heads, case expressions, │ │ │ │ +[H|T] │ │ │ │ +{error,Reason}

Patterns are allowed in clause heads, case expressions, │ │ │ │ receive expressions, and │ │ │ │ match expressions.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ The Compound Pattern Operator │ │ │ │

│ │ │ │

If Pattern1 and Pattern2 are valid patterns, the following is also a valid │ │ │ │ pattern:

Pattern1 = Pattern2

When matched against a term, both Pattern1 and Pattern2 are matched against │ │ │ │ -the term. The idea behind this feature is to avoid reconstruction of terms.

Example:

f({connect,From,To,Number,Options}, To) ->
│ │ │ │ -    Signal = {connect,From,To,Number,Options},
│ │ │ │ +the term. The idea behind this feature is to avoid reconstruction of terms.

Example:

f({connect,From,To,Number,Options}, To) ->
│ │ │ │ +    Signal = {connect,From,To,Number,Options},
│ │ │ │      ...;
│ │ │ │ -f(Signal, To) ->
│ │ │ │ -    ignore.

can instead be written as

f({connect,_,To,_,_} = Signal, To) ->
│ │ │ │ +f(Signal, To) ->
│ │ │ │ +    ignore.

can instead be written as

f({connect,_,To,_,_} = Signal, To) ->
│ │ │ │      ...;
│ │ │ │ -f(Signal, To) ->
│ │ │ │ +f(Signal, To) ->
│ │ │ │      ignore.

The compound pattern operator does not imply that its operands are matched in │ │ │ │ any particular order. That means that it is not legal to bind a variable in │ │ │ │ Pattern1 and use it in Pattern2, or vice versa.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ String Prefix in Patterns │ │ │ │

│ │ │ │ -

When matching strings, the following is a valid pattern:

f("prefix" ++ Str) -> ...

This is syntactic sugar for the equivalent, but harder to read:

f([$p,$r,$e,$f,$i,$x | Str]) -> ...

│ │ │ │ +

When matching strings, the following is a valid pattern:

f("prefix" ++ Str) -> ...

This is syntactic sugar for the equivalent, but harder to read:

f([$p,$r,$e,$f,$i,$x | Str]) -> ...

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Expressions in Patterns │ │ │ │

│ │ │ │

An arithmetic expression can be used within a pattern if it meets both of the │ │ │ │ -following two conditions:

  • It uses only numeric or bitwise operators.
  • Its value can be evaluated to a constant when complied.

Example:

case {Value, Result} of
│ │ │ │ -    {?THRESHOLD+1, ok} -> ...

│ │ │ │ +following two conditions:

  • It uses only numeric or bitwise operators.
  • Its value can be evaluated to a constant when complied.

Example:

case {Value, Result} of
│ │ │ │ +    {?THRESHOLD+1, ok} -> ...

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ The Match Operator │ │ │ │

│ │ │ │

The following matches Pattern against Expr:

Pattern = Expr

If the matching succeeds, any unbound variable in the pattern becomes bound and │ │ │ │ the value of Expr is returned.

If multiple match operators are applied in sequence, they will be evaluated from │ │ │ │ -right to left.

If the matching fails, a badmatch run-time error occurs.

Examples:

1> {A, B} = T = {answer, 42}.
│ │ │ │ -{answer,42}
│ │ │ │ +right to left.

If the matching fails, a badmatch run-time error occurs.

Examples:

1> {A, B} = T = {answer, 42}.
│ │ │ │ +{answer,42}
│ │ │ │  2> A.
│ │ │ │  answer
│ │ │ │  3> B.
│ │ │ │  42
│ │ │ │  4> T.
│ │ │ │ -{answer,42}
│ │ │ │ -5> {C, D} = [1, 2].
│ │ │ │ +{answer,42}
│ │ │ │ +5> {C, D} = [1, 2].
│ │ │ │  ** exception error: no match of right-hand side value [1,2]

Because multiple match operators are evaluated from right to left, it means │ │ │ │ that:

Pattern1 = Pattern2 = . . . = PatternN = Expression

is equivalent to:

Temporary = Expression,
│ │ │ │  PatternN = Temporary,
│ │ │ │     .
│ │ │ │     .
│ │ │ │     .,
│ │ │ │  Pattern2 = Temporary,
│ │ │ │ @@ -144,30 +144,30 @@
│ │ │ │  can safely be skipped on a first reading.

The = character is used to denote two similar but distinct operators: the │ │ │ │ match operator and the compound pattern operator. Which one is meant is │ │ │ │ determined by context.

The compound pattern operator is used to construct a compound pattern from two │ │ │ │ patterns. Compound patterns are accepted everywhere a pattern is accepted. A │ │ │ │ compound pattern matches if all of its constituent patterns match. It is not │ │ │ │ legal for a pattern that is part of a compound pattern to use variables (as keys │ │ │ │ in map patterns or sizes in binary patterns) bound in other sub patterns of the │ │ │ │ -same compound pattern.

Examples:

1> fun(#{Key := Value} = #{key := Key}) -> Value end.
│ │ │ │ +same compound pattern.

Examples:

1> fun(#{Key := Value} = #{key := Key}) -> Value end.
│ │ │ │  * 1:7: variable 'Key' is unbound
│ │ │ │ -2> F = fun({A, B} = E) -> {E, A + B} end, F({1,2}).
│ │ │ │ -{{1,2},3}
│ │ │ │ -3> G = fun(<<A:8,B:8>> = <<C:16>>) -> {A, B, C} end, G(<<42,43>>).
│ │ │ │ -{42,43,10795}

The match operator is allowed everywhere an expression is allowed. It is used │ │ │ │ +2> F = fun({A, B} = E) -> {E, A + B} end, F({1,2}). │ │ │ │ +{{1,2},3} │ │ │ │ +3> G = fun(<<A:8,B:8>> = <<C:16>>) -> {A, B, C} end, G(<<42,43>>). │ │ │ │ +{42,43,10795}

The match operator is allowed everywhere an expression is allowed. It is used │ │ │ │ to match the value of an expression to a pattern. If multiple match operators │ │ │ │ -are applied in sequence, they will be evaluated from right to left.

Examples:

1> M = #{key => key2, key2 => value}.
│ │ │ │ -#{key => key2,key2 => value}
│ │ │ │ -2> f(Key), #{Key := Value} = #{key := Key} = M, Value.
│ │ │ │ +are applied in sequence, they will be evaluated from right to left.

Examples:

1> M = #{key => key2, key2 => value}.
│ │ │ │ +#{key => key2,key2 => value}
│ │ │ │ +2> f(Key), #{Key := Value} = #{key := Key} = M, Value.
│ │ │ │  value
│ │ │ │ -3> f(Key), #{Key := Value} = (#{key := Key} = M), Value.
│ │ │ │ +3> f(Key), #{Key := Value} = (#{key := Key} = M), Value.
│ │ │ │  value
│ │ │ │ -4> f(Key), (#{Key := Value} = #{key := Key}) = M, Value.
│ │ │ │ +4> f(Key), (#{Key := Value} = #{key := Key}) = M, Value.
│ │ │ │  * 1:12: variable 'Key' is unbound
│ │ │ │ -5> <<X:Y>> = begin Y = 8, <<42:8>> end, X.
│ │ │ │ +5> <<X:Y>> = begin Y = 8, <<42:8>> end, X.
│ │ │ │  42

The expression at prompt 2> first matches the value of variable M against │ │ │ │ pattern #{key := Key}, binding variable Key. It then matches the value of │ │ │ │ M against pattern #{Key := Value} using variable Key as the key, binding │ │ │ │ variable Value.

The expression at prompt 3> matches expression (#{key := Key} = M) against │ │ │ │ pattern #{Key := Value}. The expression inside the parentheses is evaluated │ │ │ │ first. That is, M is matched against #{key := Key}, and then the value of │ │ │ │ M is matched against pattern #{Key := Value}. That is the same evaluation │ │ │ │ @@ -181,30 +181,30 @@ │ │ │ │ binding variable Y and creating a binary. The binary is then matched against │ │ │ │ pattern <<X:Y>> using the value of Y as the size of the segment.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Function Calls │ │ │ │

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

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

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

In the first form of function calls, ExprM:ExprF(Expr1,...,ExprN), each of │ │ │ │ ExprM and ExprF must be an atom or an expression that evaluates to an atom. │ │ │ │ The function is said to be called by using the fully qualified function name. │ │ │ │ -This is often referred to as a remote or external function call.

Example:

lists:keyfind(Name, 1, List)

In the second form of function calls, ExprF(Expr1,...,ExprN), ExprF must be │ │ │ │ +This is often referred to as a remote or external function call.

Example:

lists:keyfind(Name, 1, List)

In the second form of function calls, ExprF(Expr1,...,ExprN), ExprF must be │ │ │ │ an atom or evaluate to a fun.

If ExprF is an atom, the function is said to be called by using the │ │ │ │ implicitly qualified function name. If the function ExprF is locally │ │ │ │ defined, it is called. Alternatively, if ExprF is explicitly imported from the │ │ │ │ M module, M:ExprF(Expr1,...,ExprN) is called. If ExprF is neither declared │ │ │ │ locally nor explicitly imported, ExprF must be the name of an automatically │ │ │ │ -imported BIF.

Examples:

handle(Msg, State)
│ │ │ │ -spawn(m, init, [])

Examples where ExprF is a fun:

1> Fun1 = fun(X) -> X+1 end,
│ │ │ │ -Fun1(3).
│ │ │ │ +imported BIF.

Examples:

handle(Msg, State)
│ │ │ │ +spawn(m, init, [])

Examples where ExprF is a fun:

1> Fun1 = fun(X) -> X+1 end,
│ │ │ │ +Fun1(3).
│ │ │ │  4
│ │ │ │ -2> fun lists:append/2([1,2], [3,4]).
│ │ │ │ -[1,2,3,4]
│ │ │ │ +2> fun lists:append/2([1,2], [3,4]).
│ │ │ │ +[1,2,3,4]
│ │ │ │  3>

Notice that when calling a local function, there is a difference between using │ │ │ │ the implicitly or fully qualified function name. The latter always refers to the │ │ │ │ latest version of the module. See │ │ │ │ Compilation and Code Loading and │ │ │ │ Function Evaluation.

│ │ │ │ │ │ │ │ │ │ │ │ @@ -221,40 +221,40 @@ │ │ │ │ called instead. This is to avoid that future additions to the set of │ │ │ │ auto-imported BIFs do not silently change the behavior of old code.

However, to avoid that old (pre R14) code changed its behavior when compiled │ │ │ │ with Erlang/OTP version R14A or later, the following restriction applies: If you │ │ │ │ override the name of a BIF that was auto-imported in OTP versions prior to R14A │ │ │ │ (ERTS version 5.8) and have an implicitly qualified call to that function in │ │ │ │ your code, you either need to explicitly remove the auto-import using a compiler │ │ │ │ directive, or replace the call with a fully qualified function call. Otherwise │ │ │ │ -you get a compilation error. See the following example:

-export([length/1,f/1]).
│ │ │ │ +you get a compilation error. See the following example:

-export([length/1,f/1]).
│ │ │ │  
│ │ │ │ --compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
│ │ │ │ +-compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
│ │ │ │  
│ │ │ │ -length([]) ->
│ │ │ │ +length([]) ->
│ │ │ │      0;
│ │ │ │ -length([H|T]) ->
│ │ │ │ -    1 + length(T). %% Calls the local function length/1
│ │ │ │ +length([H|T]) ->
│ │ │ │ +    1 + length(T). %% Calls the local function length/1
│ │ │ │  
│ │ │ │ -f(X) when erlang:length(X) > 3 -> %% Calls erlang:length/1,
│ │ │ │ +f(X) when erlang:length(X) > 3 -> %% Calls erlang:length/1,
│ │ │ │                                    %% which is allowed in guards
│ │ │ │      long.

The same logic applies to explicitly imported functions from other modules, as │ │ │ │ to locally defined functions. It is not allowed to both import a function from │ │ │ │ -another module and have the function declared in the module at the same time:

-export([f/1]).
│ │ │ │ +another module and have the function declared in the module at the same time:

-export([f/1]).
│ │ │ │  
│ │ │ │ --compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
│ │ │ │ +-compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
│ │ │ │  
│ │ │ │ --import(mod,[length/1]).
│ │ │ │ +-import(mod,[length/1]).
│ │ │ │  
│ │ │ │ -f(X) when erlang:length(X) > 33 -> %% Calls erlang:length/1,
│ │ │ │ +f(X) when erlang:length(X) > 33 -> %% Calls erlang:length/1,
│ │ │ │                                     %% which is allowed in guards
│ │ │ │  
│ │ │ │ -    erlang:length(X);              %% Explicit call to erlang:length in body
│ │ │ │ +    erlang:length(X);              %% Explicit call to erlang:length in body
│ │ │ │  
│ │ │ │ -f(X) ->
│ │ │ │ -    length(X).                     %% mod:length/1 is called

For auto-imported BIFs added in Erlang/OTP R14A and thereafter, overriding the │ │ │ │ +f(X) -> │ │ │ │ + length(X). %% mod:length/1 is called

For auto-imported BIFs added in Erlang/OTP R14A and thereafter, overriding the │ │ │ │ name with a local function or explicit import is always allowed. However, if the │ │ │ │ -compile({no_auto_import,[F/A]) directive is not used, the compiler issues a │ │ │ │ warning whenever the function is called in the module using the implicitly │ │ │ │ qualified function name.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -266,40 +266,40 @@ │ │ │ │ ...; │ │ │ │ GuardSeqN -> │ │ │ │ BodyN │ │ │ │ end

The branches of an if-expression are scanned sequentially until a guard │ │ │ │ sequence GuardSeq that evaluates to true is found. Then the corresponding │ │ │ │ Body (a sequence of expressions separated by ,) is evaluated.

The return value of Body is the return value of the if expression.

If no guard sequence is evaluated as true, an if_clause run-time error occurs. │ │ │ │ If necessary, the guard expression true can be used in the last branch, as │ │ │ │ -that guard sequence is always true.

Example:

is_greater_than(X, Y) ->
│ │ │ │ +that guard sequence is always true.

Example:

is_greater_than(X, Y) ->
│ │ │ │      if
│ │ │ │          X > Y ->
│ │ │ │              true;
│ │ │ │          true -> % works as an 'else' branch
│ │ │ │              false
│ │ │ │      end

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Case │ │ │ │

│ │ │ │
case Expr of
│ │ │ │ -    Pattern1 [when GuardSeq1] ->
│ │ │ │ +    Pattern1 [when GuardSeq1] ->
│ │ │ │          Body1;
│ │ │ │      ...;
│ │ │ │ -    PatternN [when GuardSeqN] ->
│ │ │ │ +    PatternN [when GuardSeqN] ->
│ │ │ │          BodyN
│ │ │ │  end

The expression Expr is evaluated and the patterns Pattern are sequentially │ │ │ │ matched against the result. If a match succeeds and the optional guard sequence │ │ │ │ GuardSeq is true, the corresponding Body is evaluated.

The return value of Body is the return value of the case expression.

If there is no matching pattern with a true guard sequence, a case_clause │ │ │ │ -run-time error occurs.

Example:

is_valid_signal(Signal) ->
│ │ │ │ +run-time error occurs.

Example:

is_valid_signal(Signal) ->
│ │ │ │      case Signal of
│ │ │ │ -        {signal, _What, _From, _To} ->
│ │ │ │ +        {signal, _What, _From, _To} ->
│ │ │ │              true;
│ │ │ │ -        {signal, _What, _To} ->
│ │ │ │ +        {signal, _What, _To} ->
│ │ │ │              true;
│ │ │ │          _Else ->
│ │ │ │              false
│ │ │ │      end.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -317,57 +317,57 @@ │ │ │ │ the top-level of a maybe block. It matches the pattern Expr1 against │ │ │ │ Expr2. If the matching succeeds, any unbound variable in the pattern becomes │ │ │ │ bound. If the expression is the last expression in the maybe block, it also │ │ │ │ returns the value of Expr2. If the matching is unsuccessful, the rest of the │ │ │ │ expressions in the maybe block are skipped and the return value of the maybe │ │ │ │ block is Expr2.

None of the variables bound in a maybe block must be used in the code that │ │ │ │ follows the block.

Here is an example:

maybe
│ │ │ │ -    {ok, A} ?= a(),
│ │ │ │ +    {ok, A} ?= a(),
│ │ │ │      true = A >= 0,
│ │ │ │ -    {ok, B} ?= b(),
│ │ │ │ +    {ok, B} ?= b(),
│ │ │ │      A + B
│ │ │ │  end

Let us first assume that a() returns {ok,42} and b() returns {ok,58}. │ │ │ │ With those return values, all of the match operators will succeed, and the │ │ │ │ return value of the maybe block is A + B, which is equal to 42 + 58 = 100.

Now let us assume that a() returns error. The conditional match operator in │ │ │ │ {ok, A} ?= a() fails to match, and the return value of the maybe block is │ │ │ │ the value of the expression that failed to match, namely error. Similarly, if │ │ │ │ b() returns wrong, the return value of the maybe block is wrong.

Finally, let us assume that a() returns {ok,-1}. Because true = A >= 0 uses │ │ │ │ the match operator =, a {badmatch,false} run-time error occurs when the │ │ │ │ -expression fails to match the pattern.

The example can be written in a less succient way using nested case expressions:

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

The example can be written in a less succient way using nested case expressions:

case a() of
│ │ │ │ +    {ok, A} ->
│ │ │ │          true = A >= 0,
│ │ │ │ -        case b() of
│ │ │ │ -            {ok, B} ->
│ │ │ │ +        case b() of
│ │ │ │ +            {ok, B} ->
│ │ │ │                  A + B;
│ │ │ │              Other1 ->
│ │ │ │                  Other1
│ │ │ │          end;
│ │ │ │      Other2 ->
│ │ │ │          Other2
│ │ │ │  end

The maybe block can be augmented with else clauses:

maybe
│ │ │ │      Expr1,
│ │ │ │      ...,
│ │ │ │      ExprN
│ │ │ │  else
│ │ │ │ -    Pattern1 [when GuardSeq1] ->
│ │ │ │ +    Pattern1 [when GuardSeq1] ->
│ │ │ │          Body1;
│ │ │ │      ...;
│ │ │ │ -    PatternN [when GuardSeqN] ->
│ │ │ │ +    PatternN [when GuardSeqN] ->
│ │ │ │          BodyN
│ │ │ │  end

If a conditional match operator fails, the failed expression is matched against │ │ │ │ the patterns in all clauses between the else and end keywords. If a match │ │ │ │ succeeds and the optional guard sequence GuardSeq is true, the corresponding │ │ │ │ Body is evaluated. The value returned from the body is the return value of the │ │ │ │ maybe block.

If there is no matching pattern with a true guard sequence, an else_clause │ │ │ │ run-time error occurs.

None of the variables bound in a maybe block must be used in the else │ │ │ │ clauses. None of the variables bound in the else clauses must be used in the │ │ │ │ code that follows the maybe block.

Here is the previous example augmented with else clauses:

maybe
│ │ │ │ -    {ok, A} ?= a(),
│ │ │ │ +    {ok, A} ?= a(),
│ │ │ │      true = A >= 0,
│ │ │ │ -    {ok, B} ?= b(),
│ │ │ │ +    {ok, B} ?= b(),
│ │ │ │      A + B
│ │ │ │  else
│ │ │ │      error -> error;
│ │ │ │      wrong -> error
│ │ │ │  end

The else clauses translate the failing value from the conditional match │ │ │ │ operators to the value error. If the failing value is not one of the │ │ │ │ recognized values, a else_clause run-time error occurs.

│ │ │ │ @@ -386,75 +386,75 @@ │ │ │ │ {Name,Node} (or a pid located at another node), also never fails.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Receive │ │ │ │

│ │ │ │
receive
│ │ │ │ -    Pattern1 [when GuardSeq1] ->
│ │ │ │ +    Pattern1 [when GuardSeq1] ->
│ │ │ │          Body1;
│ │ │ │      ...;
│ │ │ │ -    PatternN [when GuardSeqN] ->
│ │ │ │ +    PatternN [when GuardSeqN] ->
│ │ │ │          BodyN
│ │ │ │  end

Fetches a received message present in the message queue of the process. The │ │ │ │ first message in the message queue is matched sequentially against the patterns │ │ │ │ from top to bottom. If no match was found, the matching sequence is repeated for │ │ │ │ the second message in the queue, and so on. Messages are queued in the │ │ │ │ order they were received. If a match │ │ │ │ succeeds, that is, if the Pattern matches and the optional guard sequence │ │ │ │ GuardSeq is true, then the message is removed from the message queue and the │ │ │ │ corresponding Body is evaluated. All other messages in the message queue │ │ │ │ remain unchanged.

The return value of Body is the return value of the receive expression.

receive never fails. The execution is suspended, possibly indefinitely, until │ │ │ │ a message arrives that matches one of the patterns and with a true guard │ │ │ │ -sequence.

Example:

wait_for_onhook() ->
│ │ │ │ +sequence.

Example:

wait_for_onhook() ->
│ │ │ │      receive
│ │ │ │          onhook ->
│ │ │ │ -            disconnect(),
│ │ │ │ -            idle();
│ │ │ │ -        {connect, B} ->
│ │ │ │ -            B ! {busy, self()},
│ │ │ │ -            wait_for_onhook()
│ │ │ │ +            disconnect(),
│ │ │ │ +            idle();
│ │ │ │ +        {connect, B} ->
│ │ │ │ +            B ! {busy, self()},
│ │ │ │ +            wait_for_onhook()
│ │ │ │      end.

The receive expression can be augmented with a timeout:

receive
│ │ │ │ -    Pattern1 [when GuardSeq1] ->
│ │ │ │ +    Pattern1 [when GuardSeq1] ->
│ │ │ │          Body1;
│ │ │ │      ...;
│ │ │ │ -    PatternN [when GuardSeqN] ->
│ │ │ │ +    PatternN [when GuardSeqN] ->
│ │ │ │          BodyN
│ │ │ │  after
│ │ │ │      ExprT ->
│ │ │ │          BodyT
│ │ │ │  end

receive...after works exactly as receive, except that if no matching message │ │ │ │ has arrived within ExprT milliseconds, then BodyT is evaluated instead. The │ │ │ │ return value of BodyT then becomes the return value of the receive...after │ │ │ │ expression. ExprT is to evaluate to an integer, or the atom infinity. The │ │ │ │ allowed integer range is from 0 to 4294967295, that is, the longest possible │ │ │ │ timeout is almost 50 days. With a zero value the timeout occurs immediately if │ │ │ │ there is no matching message in the message queue.

The atom infinity will make the process wait indefinitely for a matching │ │ │ │ message. This is the same as not using a timeout. It can be useful for timeout │ │ │ │ -values that are calculated at runtime.

Example:

wait_for_onhook() ->
│ │ │ │ +values that are calculated at runtime.

Example:

wait_for_onhook() ->
│ │ │ │      receive
│ │ │ │          onhook ->
│ │ │ │ -            disconnect(),
│ │ │ │ -            idle();
│ │ │ │ -        {connect, B} ->
│ │ │ │ -            B ! {busy, self()},
│ │ │ │ -            wait_for_onhook()
│ │ │ │ +            disconnect(),
│ │ │ │ +            idle();
│ │ │ │ +        {connect, B} ->
│ │ │ │ +            B ! {busy, self()},
│ │ │ │ +            wait_for_onhook()
│ │ │ │      after
│ │ │ │          60000 ->
│ │ │ │ -            disconnect(),
│ │ │ │ -            error()
│ │ │ │ +            disconnect(),
│ │ │ │ +            error()
│ │ │ │      end.

It is legal to use a receive...after expression with no branches:

receive
│ │ │ │  after
│ │ │ │      ExprT ->
│ │ │ │          BodyT
│ │ │ │  end

This construction does not consume any messages, only suspends execution in the │ │ │ │ -process for ExprT milliseconds. This can be used to implement simple timers.

Example:

timer() ->
│ │ │ │ -    spawn(m, timer, [self()]).
│ │ │ │ +process for ExprT milliseconds. This can be used to implement simple timers.

Example:

timer() ->
│ │ │ │ +    spawn(m, timer, [self()]).
│ │ │ │  
│ │ │ │ -timer(Pid) ->
│ │ │ │ +timer(Pid) ->
│ │ │ │      receive
│ │ │ │      after
│ │ │ │          5000 ->
│ │ │ │              Pid ! timeout
│ │ │ │      end.

For more information on timers in Erlang in general, see the │ │ │ │ Timers section of the │ │ │ │ Time and Time Correction in Erlang │ │ │ │ @@ -496,21 +496,21 @@ │ │ │ │ false │ │ │ │ 4> 0.0 =:= -0.0. │ │ │ │ false │ │ │ │ 5> 0.0 =:= +0.0. │ │ │ │ true │ │ │ │ 6> 1 > a. │ │ │ │ false │ │ │ │ -7> #{c => 3} > #{a => 1, b => 2}. │ │ │ │ +7> #{c => 3} > #{a => 1, b => 2}. │ │ │ │ false │ │ │ │ -8> #{a => 1, b => 2} == #{a => 1.0, b => 2.0}. │ │ │ │ +8> #{a => 1, b => 2} == #{a => 1.0, b => 2.0}. │ │ │ │ true │ │ │ │ -9> <<2:2>> < <<128>>. │ │ │ │ +9> <<2:2>> < <<128>>. │ │ │ │ true │ │ │ │ -10> <<3:2>> < <<128>>. │ │ │ │ +10> <<3:2>> < <<128>>. │ │ │ │ false

Note

Prior to OTP 27, the term equivalence operators considered 0.0 │ │ │ │ and -0.0 to be the same term.

This was changed in OTP 27 but legacy code may have expected them to be │ │ │ │ considered the same. To help users catch errors that may arise from an │ │ │ │ upgrade, the compiler raises a warning when 0.0 is pattern-matched or used │ │ │ │ in a term equivalence test.

If you need to match 0.0 specifically, the warning can be silenced by │ │ │ │ writing +0.0 instead, which produces the same term but makes the compiler │ │ │ │ interpret the match as being done on purpose.

│ │ │ │ @@ -536,15 +536,15 @@ │ │ │ │ 0 │ │ │ │ 8> 2#10 bor 2#01. │ │ │ │ 3 │ │ │ │ 9> a + 10. │ │ │ │ ** exception error: an error occurred when evaluating an arithmetic expression │ │ │ │ in operator +/2 │ │ │ │ called as a + 10 │ │ │ │ -10> 1 bsl (1 bsl 64). │ │ │ │ +10> 1 bsl (1 bsl 64). │ │ │ │ ** exception error: a system limit has been reached │ │ │ │ in operator bsl/2 │ │ │ │ called as 1 bsl 18446744073709551616

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Boolean Expressions │ │ │ │ @@ -563,136 +563,136 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Short-Circuit Expressions │ │ │ │

│ │ │ │
Expr1 orelse Expr2
│ │ │ │  Expr1 andalso Expr2

Expr2 is evaluated only if necessary. That is, Expr2 is evaluated only if:

  • Expr1 evaluates to false in an orelse expression.

or

  • Expr1 evaluates to true in an andalso expression.

Returns either the value of Expr1 (that is, true or false) or the value of │ │ │ │ -Expr2 (if Expr2 is evaluated).

Example 1:

case A >= -1.0 andalso math:sqrt(A+1) > B of

This works even if A is less than -1.0, since in that case, math:sqrt/1 is │ │ │ │ -never evaluated.

Example 2:

OnlyOne = is_atom(L) orelse
│ │ │ │ -         (is_list(L) andalso length(L) == 1),

Expr2 is not required to evaluate to a Boolean value. Because of that, │ │ │ │ -andalso and orelse are tail-recursive.

Example 3 (tail-recursive function):

all(Pred, [Hd|Tail]) ->
│ │ │ │ -    Pred(Hd) andalso all(Pred, Tail);
│ │ │ │ -all(_, []) ->
│ │ │ │ +Expr2 (if Expr2 is evaluated).

Example 1:

case A >= -1.0 andalso math:sqrt(A+1) > B of

This works even if A is less than -1.0, since in that case, math:sqrt/1 is │ │ │ │ +never evaluated.

Example 2:

OnlyOne = is_atom(L) orelse
│ │ │ │ +         (is_list(L) andalso length(L) == 1),

Expr2 is not required to evaluate to a Boolean value. Because of that, │ │ │ │ +andalso and orelse are tail-recursive.

Example 3 (tail-recursive function):

all(Pred, [Hd|Tail]) ->
│ │ │ │ +    Pred(Hd) andalso all(Pred, Tail);
│ │ │ │ +all(_, []) ->
│ │ │ │      true.

Change

Before Erlang/OTP R13A, Expr2 was required to evaluate to a Boolean value, │ │ │ │ and as consequence, andalso and orelse were not tail-recursive.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ List Operations │ │ │ │

│ │ │ │
Expr1 ++ Expr2
│ │ │ │  Expr1 -- Expr2

The list concatenation operator ++ appends its second argument to its first │ │ │ │ and returns the resulting list.

The list subtraction operator -- produces a list that is a copy of the first │ │ │ │ argument. The procedure is as follows: for each element in the second argument, │ │ │ │ -the first occurrence of this element (if any) is removed.

Example:

1> [1,2,3] ++ [4,5].
│ │ │ │ -[1,2,3,4,5]
│ │ │ │ -2> [1,2,3,2,1,2] -- [2,1,2].
│ │ │ │ -[3,1,2]

│ │ │ │ +the first occurrence of this element (if any) is removed.

Example:

1> [1,2,3] ++ [4,5].
│ │ │ │ +[1,2,3,4,5]
│ │ │ │ +2> [1,2,3,2,1,2] -- [2,1,2].
│ │ │ │ +[3,1,2]

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Map Expressions │ │ │ │

│ │ │ │

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Creating Maps │ │ │ │

│ │ │ │

Constructing a new map is done by letting an expression K be associated with │ │ │ │ -another expression V:

#{K => V}

New maps can include multiple associations at construction by listing every │ │ │ │ -association:

#{K1 => V1, ..., Kn => Vn}

An empty map is constructed by not associating any terms with each other:

#{}

All keys and values in the map are terms. Any expression is first evaluated and │ │ │ │ +another expression V:

#{K => V}

New maps can include multiple associations at construction by listing every │ │ │ │ +association:

#{K1 => V1, ..., Kn => Vn}

An empty map is constructed by not associating any terms with each other:

#{}

All keys and values in the map are terms. Any expression is first evaluated and │ │ │ │ then the resulting terms are used as key and value respectively.

Keys and values are separated by the => arrow and associations are separated │ │ │ │ -by a comma (,).

Examples:

M0 = #{},                 % empty map
│ │ │ │ -M1 = #{a => <<"hello">>}, % single association with literals
│ │ │ │ -M2 = #{1 => 2, b => b},   % multiple associations with literals
│ │ │ │ -M3 = #{k => {A,B}},       % single association with variables
│ │ │ │ -M4 = #{{"w", 1} => f()}.  % compound key associated with an evaluated expression

Here, A and B are any expressions and M0 through M4 are the resulting │ │ │ │ -map terms.

If two matching keys are declared, the latter key takes precedence.

Example:

1> #{1 => a, 1 => b}.
│ │ │ │ -#{1 => b }
│ │ │ │ -2> #{1.0 => a, 1 => b}.
│ │ │ │ -#{1 => b, 1.0 => a}

The order in which the expressions constructing the keys (and their associated │ │ │ │ +by a comma (,).

Examples:

M0 = #{},                 % empty map
│ │ │ │ +M1 = #{a => <<"hello">>}, % single association with literals
│ │ │ │ +M2 = #{1 => 2, b => b},   % multiple associations with literals
│ │ │ │ +M3 = #{k => {A,B}},       % single association with variables
│ │ │ │ +M4 = #{{"w", 1} => f()}.  % compound key associated with an evaluated expression

Here, A and B are any expressions and M0 through M4 are the resulting │ │ │ │ +map terms.

If two matching keys are declared, the latter key takes precedence.

Example:

1> #{1 => a, 1 => b}.
│ │ │ │ +#{1 => b }
│ │ │ │ +2> #{1.0 => a, 1 => b}.
│ │ │ │ +#{1 => b, 1.0 => a}

The order in which the expressions constructing the keys (and their associated │ │ │ │ values) are evaluated is not defined. The syntactic order of the key-value pairs │ │ │ │ in the construction is of no relevance, except in the recently mentioned case of │ │ │ │ two matching keys.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Updating Maps │ │ │ │

│ │ │ │

Updating a map has a similar syntax as constructing it.

An expression defining the map to be updated is put in front of the expression │ │ │ │ -defining the keys to be updated and their respective values:

M#{K => V}

Here M is a term of type map and K and V are any expression.

If key K does not match any existing key in the map, a new association is │ │ │ │ +defining the keys to be updated and their respective values:

M#{K => V}

Here M is a term of type map and K and V are any expression.

If key K does not match any existing key in the map, a new association is │ │ │ │ created from key K to value V.

If key K matches an existing key in map M, its associated value is replaced │ │ │ │ by the new value V. In both cases, the evaluated map expression returns a new │ │ │ │ -map.

If M is not of type map, an exception of type badmap is raised.

To only update an existing value, the following syntax is used:

M#{K := V}

Here M is a term of type map, V is an expression and K is an expression │ │ │ │ +map.

If M is not of type map, an exception of type badmap is raised.

To only update an existing value, the following syntax is used:

M#{K := V}

Here M is a term of type map, V is an expression and K is an expression │ │ │ │ that evaluates to an existing key in M.

If key K does not match any existing keys in map M, an exception of type │ │ │ │ badkey is raised at runtime. If a matching key K is present in map M, │ │ │ │ its associated value is replaced by the new value V, and the evaluated map │ │ │ │ -expression returns a new map.

If M is not of type map, an exception of type badmap is raised.

Examples:

M0 = #{},
│ │ │ │ -M1 = M0#{a => 0},
│ │ │ │ -M2 = M1#{a => 1, b => 2},
│ │ │ │ -M3 = M2#{"function" => fun() -> f() end},
│ │ │ │ -M4 = M3#{a := 2, b := 3}.  % 'a' and 'b' was added in `M1` and `M2`.

Here M0 is any map. It follows that M1 through M4 are maps as well.

More examples:

1> M = #{1 => a}.
│ │ │ │ -#{1 => a }
│ │ │ │ -2> M#{1.0 => b}.
│ │ │ │ -#{1 => a, 1.0 => b}.
│ │ │ │ -3> M#{1 := b}.
│ │ │ │ -#{1 => b}
│ │ │ │ -4> M#{1.0 := b}.
│ │ │ │ +expression returns a new map.

If M is not of type map, an exception of type badmap is raised.

Examples:

M0 = #{},
│ │ │ │ +M1 = M0#{a => 0},
│ │ │ │ +M2 = M1#{a => 1, b => 2},
│ │ │ │ +M3 = M2#{"function" => fun() -> f() end},
│ │ │ │ +M4 = M3#{a := 2, b := 3}.  % 'a' and 'b' was added in `M1` and `M2`.

Here M0 is any map. It follows that M1 through M4 are maps as well.

More examples:

1> M = #{1 => a}.
│ │ │ │ +#{1 => a }
│ │ │ │ +2> M#{1.0 => b}.
│ │ │ │ +#{1 => a, 1.0 => b}.
│ │ │ │ +3> M#{1 := b}.
│ │ │ │ +#{1 => b}
│ │ │ │ +4> M#{1.0 := b}.
│ │ │ │  ** exception error: bad argument

As in construction, the order in which the key and value expressions are │ │ │ │ evaluated is not defined. The syntactic order of the key-value pairs in the │ │ │ │ update is of no relevance, except in the case where two keys match. In that │ │ │ │ case, the latter value is used.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Maps in Patterns │ │ │ │

│ │ │ │ -

Matching of key-value associations from maps is done as follows:

#{K := V} = M

Here M is any map. The key K must be a │ │ │ │ +

Matching of key-value associations from maps is done as follows:

#{K := V} = M

Here M is any map. The key K must be a │ │ │ │ guard expression, with all variables already │ │ │ │ bound. V can be any pattern with either bound or unbound variables.

If the variable V is unbound, it becomes bound to the value associated with │ │ │ │ the key K, which must exist in the map M. If the variable V is bound, it │ │ │ │ must match the value associated with K in M.

Change

Before Erlang/OTP 23, the expression defining the key K was restricted to be │ │ │ │ -either a single variable or a literal.

Example:

1> M = #{"tuple" => {1,2}}.
│ │ │ │ -#{"tuple" => {1,2}}
│ │ │ │ -2> #{"tuple" := {1,B}} = M.
│ │ │ │ -#{"tuple" => {1,2}}
│ │ │ │ +either a single variable or a literal.

Example:

1> M = #{"tuple" => {1,2}}.
│ │ │ │ +#{"tuple" => {1,2}}
│ │ │ │ +2> #{"tuple" := {1,B}} = M.
│ │ │ │ +#{"tuple" => {1,2}}
│ │ │ │  3> B.
│ │ │ │ -2.

This binds variable B to integer 2.

Similarly, multiple values from the map can be matched:

#{K1 := V1, ..., Kn := Vn} = M

Here keys K1 through Kn are any expressions with literals or bound │ │ │ │ +2.

This binds variable B to integer 2.

Similarly, multiple values from the map can be matched:

#{K1 := V1, ..., Kn := Vn} = M

Here keys K1 through Kn are any expressions with literals or bound │ │ │ │ variables. If all key expressions evaluate successfully and all keys │ │ │ │ exist in map M, all variables in V1 .. Vn is matched to the │ │ │ │ associated values of their respective keys.

If the matching conditions are not met the match fails.

Note that when matching a map, only the := operator (not the =>) is allowed │ │ │ │ as a delimiter for the associations.

The order in which keys are declared in matching has no relevance.

Duplicate keys are allowed in matching and match each pattern associated to the │ │ │ │ -keys:

#{K := V1, K := V2} = M

The empty map literal (#{}) matches any map when used as a pattern:

#{} = Expr

This expression matches if the expression Expr is of type map, otherwise it │ │ │ │ -fails with an exception badmatch.

Here the key to be retrieved is constructed from an expression:

#{{tag,length(List)} := V} = Map

List must be an already bound variable.

Matching Syntax

Matching of literals as keys are allowed in function heads:

%% only start if not_started
│ │ │ │ -handle_call(start, From, #{state := not_started} = S) ->
│ │ │ │ +keys:

#{K := V1, K := V2} = M

The empty map literal (#{}) matches any map when used as a pattern:

#{} = Expr

This expression matches if the expression Expr is of type map, otherwise it │ │ │ │ +fails with an exception badmatch.

Here the key to be retrieved is constructed from an expression:

#{{tag,length(List)} := V} = Map

List must be an already bound variable.

Matching Syntax

Matching of literals as keys are allowed in function heads:

%% only start if not_started
│ │ │ │ +handle_call(start, From, #{state := not_started} = S) ->
│ │ │ │  ...
│ │ │ │ -    {reply, ok, S#{state := start}};
│ │ │ │ +    {reply, ok, S#{state := start}};
│ │ │ │  
│ │ │ │  %% only change if started
│ │ │ │ -handle_call(change, From, #{state := start} = S) ->
│ │ │ │ +handle_call(change, From, #{state := start} = S) ->
│ │ │ │  ...
│ │ │ │ -    {reply, ok, S#{state := changed}};

│ │ │ │ + {reply, ok, S#{state := changed}};

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Maps in Guards │ │ │ │

│ │ │ │

Maps are allowed in guards as long as all subexpressions are valid guard │ │ │ │ expressions.

The following guard BIFs handle maps:

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Bit Syntax Expressions │ │ │ │

│ │ │ │

The bit syntax operates on bit strings. A bit string is a sequence of bits │ │ │ │ -ordered from the most significant bit to the least significant bit.

<<>>  % The empty bit string, zero length
│ │ │ │ -<<E1>>
│ │ │ │ -<<E1,...,En>>

Each element Ei specifies a segment of the bit string. The segments are │ │ │ │ +ordered from the most significant bit to the least significant bit.

<<>>  % The empty bit string, zero length
│ │ │ │ +<<E1>>
│ │ │ │ +<<E1,...,En>>

Each element Ei specifies a segment of the bit string. The segments are │ │ │ │ ordered left to right from the most significant bit to the least significant bit │ │ │ │ of the bit string.

Each segment specification Ei is a value, whose default type is integer, │ │ │ │ followed by an optional size expression and an optional type specifier list.

Ei = Value |
│ │ │ │       Value:Size |
│ │ │ │       Value/TypeSpecifierList |
│ │ │ │       Value:Size/TypeSpecifierList

When used in a bit string construction, Value is an expression that is to │ │ │ │ evaluate to an integer, float, or bit string. If the expression is not a single │ │ │ │ @@ -703,34 +703,34 @@ │ │ │ │ guard expression that evaluates to an │ │ │ │ integer. All variables in the guard expression must be already bound.

Change

Before Erlang/OTP 23, Size was restricted to be an integer or a variable │ │ │ │ bound to an integer.

The value of Size specifies the size of the segment in units (see below). The │ │ │ │ default value depends on the type (see below):

  • For integer it is 8.
  • For float it is 64.
  • For binary and bitstring it is the whole binary or bit string.

In matching, the default value for a binary or bit string segment is only valid │ │ │ │ for the last element. All other bit string or binary elements in the matching │ │ │ │ must have a size specification.

Binaries

A bit string with a length that is a multiple of 8 bits is known as a binary, │ │ │ │ which is the most common and useful type of bit string.

A binary has a canonical representation in memory. Here follows a sequence of │ │ │ │ -bytes where each byte's value is its sequence number:

<<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>>

Bit strings are a later generalization of binaries, so many texts and much │ │ │ │ -information about binaries apply just as well for bit strings.

Example:

1> <<A/binary, B/binary>> = <<"abcde">>.
│ │ │ │ +bytes where each byte's value is its sequence number:

<<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>>

Bit strings are a later generalization of binaries, so many texts and much │ │ │ │ +information about binaries apply just as well for bit strings.

Example:

1> <<A/binary, B/binary>> = <<"abcde">>.
│ │ │ │  * 1:3: a binary field without size is only allowed at the end of a binary pattern
│ │ │ │ -2> <<A:3/binary, B/binary>> = <<"abcde">>.
│ │ │ │ -<<"abcde">>
│ │ │ │ +2> <<A:3/binary, B/binary>> = <<"abcde">>.
│ │ │ │ +<<"abcde">>
│ │ │ │  3> A.
│ │ │ │ -<<"abc">>
│ │ │ │ +<<"abc">>
│ │ │ │  4> B.
│ │ │ │ -<<"de">>

For the utf8, utf16, and utf32 types, Size must not be given. The size │ │ │ │ +<<"de">>

For the utf8, utf16, and utf32 types, Size must not be given. The size │ │ │ │ of the segment is implicitly determined by the type and value itself.

TypeSpecifierList is a list of type specifiers, in any order, separated by │ │ │ │ hyphens (-). Default values are used for any omitted type specifiers.

  • Type= integer | float | binary | bytes | bitstring | bits | │ │ │ │ utf8 | utf16 | utf32 - The default is integer. bytes is a │ │ │ │ shorthand for binary and bits is a shorthand for bitstring. See below │ │ │ │ for more information about the utf types.

  • Signedness= signed | unsigned - Only matters for matching and when │ │ │ │ the type is integer. The default is unsigned.

  • Endianness= big | little | native - Specifies byte level (octet │ │ │ │ level) endianness (byte order). Native-endian means that the endianness is │ │ │ │ resolved at load time to be either big-endian or little-endian, depending on │ │ │ │ what is native for the CPU that the Erlang machine is run on. Endianness only │ │ │ │ matters when the Type is either integer, utf16, utf32, or float. The │ │ │ │ -default is big.

    <<16#1234:16/little>> = <<16#3412:16>> = <<16#34:8, 16#12:8>>
  • Unit= unit:IntegerLiteral - The allowed range is 1 through 256. │ │ │ │ +default is big.

    <<16#1234:16/little>> = <<16#3412:16>> = <<16#34:8, 16#12:8>>
  • Unit= unit:IntegerLiteral - The allowed range is 1 through 256. │ │ │ │ Defaults to 1 for integer, float, and bitstring, and to 8 for binary. │ │ │ │ For types bitstring, bits, and bytes, it is not allowed to specify a │ │ │ │ unit value different from the default value. No unit specifier must be given │ │ │ │ for the types utf8, utf16, and utf32.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -755,41 +755,41 @@ │ │ │ │ │ │ │ │ Binary segments │ │ │ │

│ │ │ │

In this section, the phrase "binary segment" refers to any one of the segment │ │ │ │ types binary, bitstring, bytes, and bits.

See also the paragraphs about Binaries.

When constructing binaries and no size is specified for a binary segment, the │ │ │ │ entire binary value is interpolated into the binary being constructed. However, │ │ │ │ the size in bits of the binary being interpolated must be evenly divisible by │ │ │ │ -the unit value for the segment; otherwise an exception is raised.

For example, the following examples all succeed:

1> <<(<<"abc">>)/bitstring>>.
│ │ │ │ -<<"abc">>
│ │ │ │ -2> <<(<<"abc">>)/binary-unit:1>>.
│ │ │ │ -<<"abc">>
│ │ │ │ -3> <<(<<"abc">>)/binary>>.
│ │ │ │ -<<"abc">>

The first two examples have a unit value of 1 for the segment, while the third │ │ │ │ +the unit value for the segment; otherwise an exception is raised.

For example, the following examples all succeed:

1> <<(<<"abc">>)/bitstring>>.
│ │ │ │ +<<"abc">>
│ │ │ │ +2> <<(<<"abc">>)/binary-unit:1>>.
│ │ │ │ +<<"abc">>
│ │ │ │ +3> <<(<<"abc">>)/binary>>.
│ │ │ │ +<<"abc">>

The first two examples have a unit value of 1 for the segment, while the third │ │ │ │ segment has a unit value of 8.

Attempting to interpolate a bit string of size 1 into a binary segment with unit │ │ │ │ -8 (the default unit for binary) fails as shown in this example:

1> <<(<<1:1>>)/binary>>.
│ │ │ │ -** exception error: bad argument

For the construction to succeed, the unit value of the segment must be 1:

2> <<(<<1:1>>)/bitstring>>.
│ │ │ │ -<<1:1>>
│ │ │ │ -3> <<(<<1:1>>)/binary-unit:1>>.
│ │ │ │ -<<1:1>>

Similarly, when matching a binary segment with no size specified, the match │ │ │ │ +8 (the default unit for binary) fails as shown in this example:

1> <<(<<1:1>>)/binary>>.
│ │ │ │ +** exception error: bad argument

For the construction to succeed, the unit value of the segment must be 1:

2> <<(<<1:1>>)/bitstring>>.
│ │ │ │ +<<1:1>>
│ │ │ │ +3> <<(<<1:1>>)/binary-unit:1>>.
│ │ │ │ +<<1:1>>

Similarly, when matching a binary segment with no size specified, the match │ │ │ │ succeeds if and only if the size in bits of the rest of the binary is evenly │ │ │ │ -divisible by the unit value:

1> <<_/binary-unit:16>> = <<"">>.
│ │ │ │ -<<>>
│ │ │ │ -2> <<_/binary-unit:16>> = <<"a">>.
│ │ │ │ +divisible by the unit value:

1> <<_/binary-unit:16>> = <<"">>.
│ │ │ │ +<<>>
│ │ │ │ +2> <<_/binary-unit:16>> = <<"a">>.
│ │ │ │  ** exception error: no match of right hand side value <<"a">>
│ │ │ │ -3> <<_/binary-unit:16>> = <<"ab">>.
│ │ │ │ -<<"ab">>
│ │ │ │ -4> <<_/binary-unit:16>> = <<"abc">>.
│ │ │ │ +3> <<_/binary-unit:16>> = <<"ab">>.
│ │ │ │ +<<"ab">>
│ │ │ │ +4> <<_/binary-unit:16>> = <<"abc">>.
│ │ │ │  ** exception error: no match of right hand side value <<"abc">>
│ │ │ │ -5> <<_/binary-unit:16>> = <<"abcd">>.
│ │ │ │ -<<"abcd">>

When a size is explicitly specified for a binary segment, the segment size in │ │ │ │ +5> <<_/binary-unit:16>> = <<"abcd">>. │ │ │ │ +<<"abcd">>

When a size is explicitly specified for a binary segment, the segment size in │ │ │ │ bits is the value of Size multiplied by the default or explicit unit value.

When constructing binaries, the size of the binary being interpolated into the │ │ │ │ -constructed binary must be at least as large as the size of the binary segment.

Examples:

1> <<(<<"abc">>):2/binary>>.
│ │ │ │ -<<"ab">>
│ │ │ │ -2> <<(<<"a">>):2/binary>>.
│ │ │ │ +constructed binary must be at least as large as the size of the binary segment.

Examples:

1> <<(<<"abc">>):2/binary>>.
│ │ │ │ +<<"ab">>
│ │ │ │ +2> <<(<<"a">>):2/binary>>.
│ │ │ │  ** exception error: construction of binary failed
│ │ │ │          *** segment 1 of type 'binary': the value <<"a">> is shorter than the size of the segment

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Unicode segments │ │ │ │

│ │ │ │ @@ -805,78 +805,78 @@ │ │ │ │ range 0 through 16#D7FF or 16#E000 through 16#10FFFF. The match fails if the │ │ │ │ returned value falls outside those ranges.

A segment of type utf8 matches 1-4 bytes in the bit string, if the bit string │ │ │ │ at the match position contains a valid UTF-8 sequence. (See RFC-3629 or the │ │ │ │ Unicode standard.)

A segment of type utf16 can match 2 or 4 bytes in the bit string. The match │ │ │ │ fails if the bit string at the match position does not contain a legal UTF-16 │ │ │ │ encoding of a Unicode code point. (See RFC-2781 or the Unicode standard.)

A segment of type utf32 can match 4 bytes in the bit string in the same way as │ │ │ │ an integer segment matches 32 bits. The match fails if the resulting integer │ │ │ │ -is outside the legal ranges previously mentioned.

Examples:

1> Bin1 = <<1,17,42>>.
│ │ │ │ -<<1,17,42>>
│ │ │ │ -2> Bin2 = <<"abc">>.
│ │ │ │ -<<97,98,99>>
│ │ │ │ +is outside the legal ranges previously mentioned.

Examples:

1> Bin1 = <<1,17,42>>.
│ │ │ │ +<<1,17,42>>
│ │ │ │ +2> Bin2 = <<"abc">>.
│ │ │ │ +<<97,98,99>>
│ │ │ │  
│ │ │ │ -3> Bin3 = <<1,17,42:16>>.
│ │ │ │ -<<1,17,0,42>>
│ │ │ │ -4> <<A,B,C:16>> = <<1,17,42:16>>.
│ │ │ │ -<<1,17,0,42>>
│ │ │ │ +3> Bin3 = <<1,17,42:16>>.
│ │ │ │ +<<1,17,0,42>>
│ │ │ │ +4> <<A,B,C:16>> = <<1,17,42:16>>.
│ │ │ │ +<<1,17,0,42>>
│ │ │ │  5> C.
│ │ │ │  42
│ │ │ │ -6> <<D:16,E,F>> = <<1,17,42:16>>.
│ │ │ │ -<<1,17,0,42>>
│ │ │ │ +6> <<D:16,E,F>> = <<1,17,42:16>>.
│ │ │ │ +<<1,17,0,42>>
│ │ │ │  7> D.
│ │ │ │  273
│ │ │ │  8> F.
│ │ │ │  42
│ │ │ │ -9> <<G,H/binary>> = <<1,17,42:16>>.
│ │ │ │ -<<1,17,0,42>>
│ │ │ │ +9> <<G,H/binary>> = <<1,17,42:16>>.
│ │ │ │ +<<1,17,0,42>>
│ │ │ │  10> H.
│ │ │ │ -<<17,0,42>>
│ │ │ │ -11> <<G,J/bitstring>> = <<1,17,42:12>>.
│ │ │ │ -<<1,17,2,10:4>>
│ │ │ │ +<<17,0,42>>
│ │ │ │ +11> <<G,J/bitstring>> = <<1,17,42:12>>.
│ │ │ │ +<<1,17,2,10:4>>
│ │ │ │  12> J.
│ │ │ │ -<<17,2,10:4>>
│ │ │ │ +<<17,2,10:4>>
│ │ │ │  
│ │ │ │ -13> <<1024/utf8>>.
│ │ │ │ -<<208,128>>
│ │ │ │ +13> <<1024/utf8>>.
│ │ │ │ +<<208,128>>
│ │ │ │  
│ │ │ │ -14> <<1:1,0:7>>.
│ │ │ │ -<<128>>
│ │ │ │ -15> <<16#123:12/little>> = <<16#231:12>> = <<2:4, 3:4, 1:4>>.
│ │ │ │ -<<35,1:4>>

Notice that bit string patterns cannot be nested.

Notice also that "B=<<1>>" is interpreted as "B =< <1>>" which is a syntax │ │ │ │ +14> <<1:1,0:7>>. │ │ │ │ +<<128>> │ │ │ │ +15> <<16#123:12/little>> = <<16#231:12>> = <<2:4, 3:4, 1:4>>. │ │ │ │ +<<35,1:4>>

Notice that bit string patterns cannot be nested.

Notice also that "B=<<1>>" is interpreted as "B =< <1>>" which is a syntax │ │ │ │ error. The correct way is to write a space after =: "B = <<1>>.

More examples are provided in Programming Examples.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Fun Expressions │ │ │ │

│ │ │ │
fun
│ │ │ │ -    [Name](Pattern11,...,Pattern1N) [when GuardSeq1] ->
│ │ │ │ +    [Name](Pattern11,...,Pattern1N) [when GuardSeq1] ->
│ │ │ │                Body1;
│ │ │ │      ...;
│ │ │ │ -    [Name](PatternK1,...,PatternKN) [when GuardSeqK] ->
│ │ │ │ +    [Name](PatternK1,...,PatternKN) [when GuardSeqK] ->
│ │ │ │                BodyK
│ │ │ │  end

A fun expression begins with the keyword fun and ends with the keyword end. │ │ │ │ Between them is to be a function declaration, similar to a │ │ │ │ regular function declaration, │ │ │ │ except that the function name is optional and is to be a variable, if any.

Variables in a fun head shadow the function name and both shadow variables in │ │ │ │ the function clause surrounding the fun expression. Variables bound in a fun │ │ │ │ -body are local to the fun body.

The return value of the expression is the resulting fun.

Examples:

1> Fun1 = fun (X) -> X+1 end.
│ │ │ │ +body are local to the fun body.

The return value of the expression is the resulting fun.

Examples:

1> Fun1 = fun (X) -> X+1 end.
│ │ │ │  #Fun<erl_eval.6.39074546>
│ │ │ │ -2> Fun1(2).
│ │ │ │ +2> Fun1(2).
│ │ │ │  3
│ │ │ │ -3> Fun2 = fun (X) when X>=5 -> gt; (X) -> lt end.
│ │ │ │ +3> Fun2 = fun (X) when X>=5 -> gt; (X) -> lt end.
│ │ │ │  #Fun<erl_eval.6.39074546>
│ │ │ │ -4> Fun2(7).
│ │ │ │ +4> Fun2(7).
│ │ │ │  gt
│ │ │ │ -5> Fun3 = fun Fact(1) -> 1; Fact(X) when X > 1 -> X * Fact(X - 1) end.
│ │ │ │ +5> Fun3 = fun Fact(1) -> 1; Fact(X) when X > 1 -> X * Fact(X - 1) end.
│ │ │ │  #Fun<erl_eval.6.39074546>
│ │ │ │ -6> Fun3(4).
│ │ │ │ +6> Fun3(4).
│ │ │ │  24

The following fun expressions are also allowed:

fun Name/Arity
│ │ │ │  fun Module:Name/Arity

In Name/Arity, Name is an atom and Arity is an integer. Name/Arity must │ │ │ │ -specify an existing local function. The expression is syntactic sugar for:

fun (Arg1,...,ArgN) -> Name(Arg1,...,ArgN) end

In Module:Name/Arity, Module, and Name are atoms and Arity is an │ │ │ │ +specify an existing local function. The expression is syntactic sugar for:

fun (Arg1,...,ArgN) -> Name(Arg1,...,ArgN) end

In Module:Name/Arity, Module, and Name are atoms and Arity is an │ │ │ │ integer. Module, Name, and Arity can also be variables. A fun defined in │ │ │ │ this way refers to the function Name with arity Arity in the latest │ │ │ │ version of module Module. A fun defined in this way is not dependent on the │ │ │ │ code for the module in which it is defined.

Change

Before Erlang/OTP R15, Module, Name, and Arity were not allowed to be │ │ │ │ variables.

More examples are provided in Programming Examples.

│ │ │ │ │ │ │ │ │ │ │ │ @@ -886,35 +886,35 @@ │ │ │ │
catch Expr

Returns the value of Expr unless an exception is raised during the evaluation. In │ │ │ │ that case, the exception is caught. The return value depends on the class of the │ │ │ │ exception:

Reason depends on the type of error that occurred, and Stack is the stack of │ │ │ │ recent function calls, see Exit Reasons.

Examples:

1> catch 1+2.
│ │ │ │  3
│ │ │ │  2> catch 1+a.
│ │ │ │ -{'EXIT',{badarith,[...]}}

The BIF throw(Any) can be used for non-local return from a │ │ │ │ -function. It must be evaluated within a catch, which returns the value Any.

Example:

3> catch throw(hello).
│ │ │ │ +{'EXIT',{badarith,[...]}}

The BIF throw(Any) can be used for non-local return from a │ │ │ │ +function. It must be evaluated within a catch, which returns the value Any.

Example:

3> catch throw(hello).
│ │ │ │  hello

If throw/1 is not evaluated within a catch, a nocatch run-time │ │ │ │ error occurs.

Change

Before Erlang/OTP 24, the catch operator had the lowest precedence, making │ │ │ │ -it necessary to add parentheses when combining it with the match operator:

1> A = (catch 42).
│ │ │ │ +it necessary to add parentheses when combining it with the match operator:

1> A = (catch 42).
│ │ │ │  42
│ │ │ │  2> A.
│ │ │ │  42

Starting from Erlang/OTP 24, the parentheses can be omitted:

1> A = catch 42.
│ │ │ │  42
│ │ │ │  2> A.
│ │ │ │  42

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Try │ │ │ │

│ │ │ │
try Exprs
│ │ │ │  catch
│ │ │ │ -    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
│ │ │ │ +    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
│ │ │ │          ExceptionBody1;
│ │ │ │ -    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
│ │ │ │ +    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
│ │ │ │          ExceptionBodyN
│ │ │ │  end

This is an enhancement of catch. It gives the │ │ │ │ possibility to:

  • Distinguish between different exception classes.
  • Choose to handle only the desired ones.
  • Passing the others on to an enclosing try or catch, or to default error │ │ │ │ handling.

Notice that although the keyword catch is used in the try expression, there │ │ │ │ is not a catch expression within the try expression.

It returns the value of Exprs (a sequence of expressions Expr1, ..., ExprN) │ │ │ │ unless an exception occurs during the evaluation. In that case the exception is │ │ │ │ caught and the patterns ExceptionPattern with the right exception class │ │ │ │ @@ -924,47 +924,47 @@ │ │ │ │ stack trace is bound to the variable when the corresponding ExceptionPattern │ │ │ │ matches.

If an exception occurs during evaluation of Exprs but there is no matching │ │ │ │ ExceptionPattern of the right Class with a true guard sequence, the │ │ │ │ exception is passed on as if Exprs had not been enclosed in a try │ │ │ │ expression.

If an exception occurs during evaluation of ExceptionBody, it is not caught.

It is allowed to omit Class and Stacktrace. An omitted Class is shorthand │ │ │ │ for throw:

try Exprs
│ │ │ │  catch
│ │ │ │ -    ExceptionPattern1 [when ExceptionGuardSeq1] ->
│ │ │ │ +    ExceptionPattern1 [when ExceptionGuardSeq1] ->
│ │ │ │          ExceptionBody1;
│ │ │ │ -    ExceptionPatternN [when ExceptionGuardSeqN] ->
│ │ │ │ +    ExceptionPatternN [when ExceptionGuardSeqN] ->
│ │ │ │          ExceptionBodyN
│ │ │ │  end

The try expression can have an of section:

try Exprs of
│ │ │ │ -    Pattern1 [when GuardSeq1] ->
│ │ │ │ +    Pattern1 [when GuardSeq1] ->
│ │ │ │          Body1;
│ │ │ │      ...;
│ │ │ │ -    PatternN [when GuardSeqN] ->
│ │ │ │ +    PatternN [when GuardSeqN] ->
│ │ │ │          BodyN
│ │ │ │  catch
│ │ │ │ -    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
│ │ │ │ +    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
│ │ │ │          ExceptionBody1;
│ │ │ │      ...;
│ │ │ │ -    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
│ │ │ │ +    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
│ │ │ │          ExceptionBodyN
│ │ │ │  end

If the evaluation of Exprs succeeds without an exception, the patterns │ │ │ │ Pattern are sequentially matched against the result in the same way as for a │ │ │ │ case expression, except that if the matching fails, a │ │ │ │ try_clause run-time error occurs instead of a case_clause.

Only exceptions occurring during the evaluation of Exprs can be caught by the │ │ │ │ catch section. Exceptions occurring in a Body or due to a failed match are │ │ │ │ not caught.

The try expression can also be augmented with an after section, intended to │ │ │ │ be used for cleanup with side effects:

try Exprs of
│ │ │ │ -    Pattern1 [when GuardSeq1] ->
│ │ │ │ +    Pattern1 [when GuardSeq1] ->
│ │ │ │          Body1;
│ │ │ │      ...;
│ │ │ │ -    PatternN [when GuardSeqN] ->
│ │ │ │ +    PatternN [when GuardSeqN] ->
│ │ │ │          BodyN
│ │ │ │  catch
│ │ │ │ -    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
│ │ │ │ +    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
│ │ │ │          ExceptionBody1;
│ │ │ │      ...;
│ │ │ │ -    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
│ │ │ │ +    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
│ │ │ │          ExceptionBodyN
│ │ │ │  after
│ │ │ │      AfterBody
│ │ │ │  end

AfterBody is evaluated after either Body or ExceptionBody, no matter which │ │ │ │ one. The evaluated value of AfterBody is lost; the return value of the try │ │ │ │ expression is the same with an after section as without.

Even if an exception occurs during evaluation of Body or ExceptionBody, │ │ │ │ AfterBody is evaluated. In this case the exception is passed on after │ │ │ │ @@ -987,40 +987,40 @@ │ │ │ │ ExpressionBody │ │ │ │ after │ │ │ │ AfterBody │ │ │ │ end │ │ │ │ │ │ │ │ try Exprs after AfterBody end

Next is an example of using after. This closes the file, even in the event of │ │ │ │ exceptions in file:read/2 or in binary_to_term/1. The │ │ │ │ -exceptions are the same as without the try...after...end expression:

termize_file(Name) ->
│ │ │ │ -    {ok,F} = file:open(Name, [read,binary]),
│ │ │ │ +exceptions are the same as without the try...after...end expression:

termize_file(Name) ->
│ │ │ │ +    {ok,F} = file:open(Name, [read,binary]),
│ │ │ │      try
│ │ │ │ -        {ok,Bin} = file:read(F, 1024*1024),
│ │ │ │ -        binary_to_term(Bin)
│ │ │ │ +        {ok,Bin} = file:read(F, 1024*1024),
│ │ │ │ +        binary_to_term(Bin)
│ │ │ │      after
│ │ │ │ -        file:close(F)
│ │ │ │ +        file:close(F)
│ │ │ │      end.

Next is an example of using try to emulate catch Expr:

try Expr
│ │ │ │  catch
│ │ │ │      throw:Term -> Term;
│ │ │ │ -    exit:Reason -> {'EXIT',Reason};
│ │ │ │ -    error:Reason:Stk -> {'EXIT',{Reason,Stk}}
│ │ │ │ +    exit:Reason -> {'EXIT',Reason};
│ │ │ │ +    error:Reason:Stk -> {'EXIT',{Reason,Stk}}
│ │ │ │  end

Variables bound in the various parts of these expressions have different scopes. │ │ │ │ Variables bound just after the try keyword are:

  • bound in the of section
  • unsafe in both the catch and after sections, as well as after the whole │ │ │ │ construct

Variables bound in of section are:

  • unbound in the catch section
  • unsafe in both the after section, as well as after the whole construct

Variables bound in the catch section are unsafe in the after section, as │ │ │ │ well as after the whole construct.

Variables bound in the after section are unsafe after the whole construct.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Parenthesized Expressions │ │ │ │

│ │ │ │ -
(Expr)

Parenthesized expressions are useful to override │ │ │ │ +

(Expr)

Parenthesized expressions are useful to override │ │ │ │ operator precedences, for example, in arithmetic │ │ │ │ expressions:

1> 1 + 2 * 3.
│ │ │ │  7
│ │ │ │ -2> (1 + 2) * 3.
│ │ │ │ +2> (1 + 2) * 3.
│ │ │ │  9

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Block Expressions │ │ │ │

│ │ │ │
begin
│ │ │ │ @@ -1032,71 +1032,71 @@
│ │ │ │    
│ │ │ │      
│ │ │ │    
│ │ │ │    Comprehensions
│ │ │ │  

│ │ │ │

Comprehensions provide a succinct notation for iterating over one or more terms │ │ │ │ and constructing a new term. Comprehensions come in three different flavors, │ │ │ │ -depending on the type of term they build.

List comprehensions construct lists. They have the following syntax:

[Expr || Qualifier1, . . ., QualifierN]

Here, Expr is an arbitrary expression, and each Qualifier is either a │ │ │ │ +depending on the type of term they build.

List comprehensions construct lists. They have the following syntax:

[Expr || Qualifier1, . . ., QualifierN]

Here, Expr is an arbitrary expression, and each Qualifier is either a │ │ │ │ generator or a filter.

Bit string comprehensions construct bit strings or binaries. They have the │ │ │ │ -following syntax:

<< BitStringExpr || Qualifier1, . . ., QualifierN >>

BitStringExpr is an expression that evaluates to a bit string. If │ │ │ │ +following syntax:

<< BitStringExpr || Qualifier1, . . ., QualifierN >>

BitStringExpr is an expression that evaluates to a bit string. If │ │ │ │ BitStringExpr is a function call, it must be enclosed in parentheses. Each │ │ │ │ -Qualifier is either a generator or a filter.

Map comprehensions construct maps. They have the following syntax:

#{KeyExpr => ValueExpr || Qualifier1, . . ., QualifierN}

Here, KeyExpr and ValueExpr are arbitrary expressions, and each Qualifier │ │ │ │ +Qualifier is either a generator or a filter.

Map comprehensions construct maps. They have the following syntax:

#{KeyExpr => ValueExpr || Qualifier1, . . ., QualifierN}

Here, KeyExpr and ValueExpr are arbitrary expressions, and each Qualifier │ │ │ │ is either a generator or a filter.

Change

Map comprehensions and map generators were introduced in Erlang/OTP 26.

There are three kinds of generators.

A list generator has the following syntax:

Pattern <- ListExpr

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

A bit string generator has the following syntax:

BitstringPattern <= BitStringExpr

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

A map generator has the following syntax:

KeyPattern := ValuePattern <- MapExpression

where MapExpr is an expression that evaluates to a map, or a map iterator │ │ │ │ obtained by calling maps:iterator/1 or maps:iterator/2.

A filter is an expression that evaluates to true or false.

The variables in the generator patterns shadow previously bound variables, │ │ │ │ including variables bound in a previous generator pattern.

Variables bound in a generator expression are not visible outside the │ │ │ │ -expression:

1> [{E,L} || E <- L=[1,2,3]].
│ │ │ │ +expression:

1> [{E,L} || E <- L=[1,2,3]].
│ │ │ │  * 1:5: variable 'L' is unbound

A list comprehension returns a list, where the list elements are the result │ │ │ │ of evaluating Expr for each combination of generator elements for which all │ │ │ │ filters are true.

A bit string comprehension returns a bit string, which is created by │ │ │ │ concatenating the results of evaluating BitStringExpr for each combination of │ │ │ │ bit string generator elements for which all filters are true.

A map comprehension returns a map, where the map elements are the result of │ │ │ │ evaluating KeyExpr and ValueExpr for each combination of generator elements │ │ │ │ for which all filters are true. If the key expressions are not unique, the last │ │ │ │ -occurrence is stored in the map.

Examples:

Multiplying each element in a list by two:

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

Multiplying each byte in a binary by two, returning a list:

1> [X*2 || <<X>> <= <<1,2,3>>].
│ │ │ │ -[2,4,6]

Multiplying each byte in a binary by two:

1> << <<(X*2)>> || <<X>> <= <<1,2,3>> >>.
│ │ │ │ -<<2,4,6>>

Multiplying each element in a list by two, returning a binary:

1> << <<(X*2)>> || X <- [1,2,3] >>.
│ │ │ │ -<<2,4,6>>

Creating a mapping from an integer to its square:

1> #{X => X*X || X <- [1,2,3]}.
│ │ │ │ -#{1 => 1,2 => 4,3 => 9}

Multiplying the value of each element in a map by two:

1> #{K => 2*V || K := V <- #{a => 1,b => 2,c => 3}}.
│ │ │ │ -#{a => 2,b => 4,c => 6}

Filtering a list, keeping odd numbers:

1> [X || X <- [1,2,3,4,5], X rem 2 =:= 1].
│ │ │ │ -[1,3,5]

Filtering a list, keeping only elements that match:

1> [X || {_,_}=X <- [{a,b}, [a], {x,y,z}, {1,2}]].
│ │ │ │ -[{a,b},{1,2}]

Combining elements from two list generators:

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

More examples are provided in │ │ │ │ +occurrence is stored in the map.

Examples:

Multiplying each element in a list by two:

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

Multiplying each byte in a binary by two, returning a list:

1> [X*2 || <<X>> <= <<1,2,3>>].
│ │ │ │ +[2,4,6]

Multiplying each byte in a binary by two:

1> << <<(X*2)>> || <<X>> <= <<1,2,3>> >>.
│ │ │ │ +<<2,4,6>>

Multiplying each element in a list by two, returning a binary:

1> << <<(X*2)>> || X <- [1,2,3] >>.
│ │ │ │ +<<2,4,6>>

Creating a mapping from an integer to its square:

1> #{X => X*X || X <- [1,2,3]}.
│ │ │ │ +#{1 => 1,2 => 4,3 => 9}

Multiplying the value of each element in a map by two:

1> #{K => 2*V || K := V <- #{a => 1,b => 2,c => 3}}.
│ │ │ │ +#{a => 2,b => 4,c => 6}

Filtering a list, keeping odd numbers:

1> [X || X <- [1,2,3,4,5], X rem 2 =:= 1].
│ │ │ │ +[1,3,5]

Filtering a list, keeping only elements that match:

1> [X || {_,_}=X <- [{a,b}, [a], {x,y,z}, {1,2}]].
│ │ │ │ +[{a,b},{1,2}]

Combining elements from two list generators:

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

More examples are provided in │ │ │ │ Programming Examples.

When there are no generators, a comprehension returns either a term constructed │ │ │ │ from a single element (the result of evaluating Expr) if all filters are true, │ │ │ │ or a term constructed from no elements (that is, [] for list comprehension, │ │ │ │ -<<>> for a bit string comprehension, and #{} for a map comprehension).

Example:

1> [2 || is_integer(2)].
│ │ │ │ -[2]
│ │ │ │ -2> [x || is_integer(x)].
│ │ │ │ -[]

What happens when the filter expression does not evaluate to a boolean value │ │ │ │ +<<>> for a bit string comprehension, and #{} for a map comprehension).

Example:

1> [2 || is_integer(2)].
│ │ │ │ +[2]
│ │ │ │ +2> [x || is_integer(x)].
│ │ │ │ +[]

What happens when the filter expression does not evaluate to a boolean value │ │ │ │ depends on the expression:

  • If the expression is a guard expression, │ │ │ │ failure to evaluate or evaluating to a non-boolean value is equivalent to │ │ │ │ evaluating to false.
  • If the expression is not a guard expression and evaluates to a non-Boolean │ │ │ │ value Val, an exception {bad_filter, Val} is triggered at runtime. If the │ │ │ │ evaluation of the expression raises an exception, it is not caught by the │ │ │ │ -comprehension.

Examples (using a guard expression as filter):

1> List = [1,2,a,b,c,3,4].
│ │ │ │ -[1,2,a,b,c,3,4]
│ │ │ │ -2> [E || E <- List, E rem 2].
│ │ │ │ -[]
│ │ │ │ -3> [E || E <- List, E rem 2 =:= 0].
│ │ │ │ -[2,4]

Examples (using a non-guard expression as filter):

1> List = [1,2,a,b,c,3,4].
│ │ │ │ -[1,2,a,b,c,3,4]
│ │ │ │ -2> FaultyIsEven = fun(E) -> E rem 2 end.
│ │ │ │ +comprehension.

Examples (using a guard expression as filter):

1> List = [1,2,a,b,c,3,4].
│ │ │ │ +[1,2,a,b,c,3,4]
│ │ │ │ +2> [E || E <- List, E rem 2].
│ │ │ │ +[]
│ │ │ │ +3> [E || E <- List, E rem 2 =:= 0].
│ │ │ │ +[2,4]

Examples (using a non-guard expression as filter):

1> List = [1,2,a,b,c,3,4].
│ │ │ │ +[1,2,a,b,c,3,4]
│ │ │ │ +2> FaultyIsEven = fun(E) -> E rem 2 end.
│ │ │ │  #Fun<erl_eval.42.17316486>
│ │ │ │ -3> [E || E <- List, FaultyIsEven(E)].
│ │ │ │ +3> [E || E <- List, FaultyIsEven(E)].
│ │ │ │  ** exception error: bad filter 1
│ │ │ │ -4> IsEven = fun(E) -> E rem 2 =:= 0 end.
│ │ │ │ +4> IsEven = fun(E) -> E rem 2 =:= 0 end.
│ │ │ │  #Fun<erl_eval.42.17316486>
│ │ │ │ -5> [E || E <- List, IsEven(E)].
│ │ │ │ +5> [E || E <- List, IsEven(E)].
│ │ │ │  ** exception error: an error occurred when evaluating an arithmetic expression
│ │ │ │       in operator  rem/2
│ │ │ │          called as a rem 2
│ │ │ │ -6> [E || E <- List, is_integer(E), IsEven(E)].
│ │ │ │ -[2,4]

│ │ │ │ +6> [E || E <- List, is_integer(E), IsEven(E)]. │ │ │ │ +[2,4]

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Guard Sequences │ │ │ │

│ │ │ │

A guard sequence is a sequence of guards, separated by semicolon (;). The │ │ │ │ guard sequence is true if at least one of the guards is true. (The remaining │ │ │ ├── OEBPS/example.xhtml │ │ │ │ @@ -26,24 +26,24 @@ │ │ │ │ │ │ │ │ Description │ │ │ │

│ │ │ │

A common interoperability situation is when you want to incorporate a piece of │ │ │ │ code, solving a complex problem, in your Erlang program. Suppose for example, │ │ │ │ that you have the following C functions that you would like to call from Erlang:

/* complex.c */
│ │ │ │  
│ │ │ │ -int foo(int x) {
│ │ │ │ +int foo(int x) {
│ │ │ │    return x+1;
│ │ │ │ -}
│ │ │ │ +}
│ │ │ │  
│ │ │ │ -int bar(int y) {
│ │ │ │ +int bar(int y) {
│ │ │ │    return y*2;
│ │ │ │ -}

The functions are deliberately kept as simple as possible, for readability │ │ │ │ +}

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.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -149,142 +149,142 @@ │ │ │ │ #include "ei.h" │ │ │ │ #include <unistd.h> │ │ │ │ #include <string.h> │ │ │ │ #include <stdlib.h> │ │ │ │ │ │ │ │ typedef unsigned char byte; │ │ │ │ │ │ │ │ -int read_cmd(byte *buf); │ │ │ │ -int write_cmd(byte *buf, int len); │ │ │ │ -int foo(int x); │ │ │ │ -int bar(int y); │ │ │ │ - │ │ │ │ -static void fail(int place) { │ │ │ │ - fprintf(stderr, "Something went wrong %d\n", place); │ │ │ │ - exit(1); │ │ │ │ -} │ │ │ │ +int read_cmd(byte *buf); │ │ │ │ +int write_cmd(byte *buf, int len); │ │ │ │ +int foo(int x); │ │ │ │ +int bar(int y); │ │ │ │ + │ │ │ │ +static void fail(int place) { │ │ │ │ + fprintf(stderr, "Something went wrong %d\n", place); │ │ │ │ + exit(1); │ │ │ │ +} │ │ │ │ │ │ │ │ -int main() { │ │ │ │ - byte buf[100]; │ │ │ │ +int main() { │ │ │ │ + byte buf[100]; │ │ │ │ int index = 0; │ │ │ │ int version = 0; │ │ │ │ int arity = 0; │ │ │ │ - char atom[128]; │ │ │ │ + char atom[128]; │ │ │ │ long in = 0; │ │ │ │ int res = 0; │ │ │ │ ei_x_buff res_buf; │ │ │ │ - ei_init(); │ │ │ │ - while (read_cmd(buf) > 0) { │ │ │ │ - if (ei_decode_version(buf, &index, &version) != 0) │ │ │ │ - fail(1); │ │ │ │ - if (ei_decode_tuple_header(buf, &index, &arity) != 0) │ │ │ │ - fail(2); │ │ │ │ - if (arity != 2) │ │ │ │ - fail(3); │ │ │ │ - if (ei_decode_atom(buf, &index, atom) != 0) │ │ │ │ - fail(4); │ │ │ │ - if (ei_decode_long(buf, &index, &in) != 0) │ │ │ │ - fail(5); │ │ │ │ - if (strncmp(atom, "foo", 3) == 0) { │ │ │ │ - res = foo((int)in); │ │ │ │ - } else if (strncmp(atom, "bar", 3) == 0) { │ │ │ │ - res = bar((int)in); │ │ │ │ - } │ │ │ │ - if (ei_x_new_with_version(&res_buf) != 0) │ │ │ │ - fail(6); │ │ │ │ - if (ei_x_encode_long(&res_buf, res) != 0) │ │ │ │ - fail(7); │ │ │ │ - write_cmd(res_buf.buff, res_buf.index); │ │ │ │ + ei_init(); │ │ │ │ + while (read_cmd(buf) > 0) { │ │ │ │ + if (ei_decode_version(buf, &index, &version) != 0) │ │ │ │ + fail(1); │ │ │ │ + if (ei_decode_tuple_header(buf, &index, &arity) != 0) │ │ │ │ + fail(2); │ │ │ │ + if (arity != 2) │ │ │ │ + fail(3); │ │ │ │ + if (ei_decode_atom(buf, &index, atom) != 0) │ │ │ │ + fail(4); │ │ │ │ + if (ei_decode_long(buf, &index, &in) != 0) │ │ │ │ + fail(5); │ │ │ │ + if (strncmp(atom, "foo", 3) == 0) { │ │ │ │ + res = foo((int)in); │ │ │ │ + } else if (strncmp(atom, "bar", 3) == 0) { │ │ │ │ + res = bar((int)in); │ │ │ │ + } │ │ │ │ + if (ei_x_new_with_version(&res_buf) != 0) │ │ │ │ + fail(6); │ │ │ │ + if (ei_x_encode_long(&res_buf, res) != 0) │ │ │ │ + fail(7); │ │ │ │ + write_cmd(res_buf.buff, res_buf.index); │ │ │ │ │ │ │ │ - if (ei_x_free(&res_buf) != 0) │ │ │ │ - fail(8); │ │ │ │ + if (ei_x_free(&res_buf) != 0) │ │ │ │ + fail(8); │ │ │ │ index = 0; │ │ │ │ - } │ │ │ │ -}

The following functions, read_cmd() and write_cmd(), from the erl_comm.c │ │ │ │ + } │ │ │ │ +}

The following functions, read_cmd() and write_cmd(), from the erl_comm.c │ │ │ │ example in Ports can still be used for reading from and writing to │ │ │ │ the port:

/* erl_comm.c */
│ │ │ │  
│ │ │ │  #include <stdio.h>
│ │ │ │  #include <unistd.h>
│ │ │ │  
│ │ │ │  typedef unsigned char byte;
│ │ │ │  
│ │ │ │ -int read_exact(byte *buf, int len)
│ │ │ │ -{
│ │ │ │ +int read_exact(byte *buf, int len)
│ │ │ │ +{
│ │ │ │    int i, got=0;
│ │ │ │  
│ │ │ │ -  do {
│ │ │ │ -      if ((i = read(0, buf+got, len-got)) <= 0){
│ │ │ │ -          return(i);
│ │ │ │ -      }
│ │ │ │ +  do {
│ │ │ │ +      if ((i = read(0, buf+got, len-got)) <= 0){
│ │ │ │ +          return(i);
│ │ │ │ +      }
│ │ │ │      got += i;
│ │ │ │ -  } while (got<len);
│ │ │ │ +  } while (got<len);
│ │ │ │  
│ │ │ │ -  return(len);
│ │ │ │ -}
│ │ │ │ +  return(len);
│ │ │ │ +}
│ │ │ │  
│ │ │ │ -int write_exact(byte *buf, int len)
│ │ │ │ -{
│ │ │ │ +int write_exact(byte *buf, int len)
│ │ │ │ +{
│ │ │ │    int i, wrote = 0;
│ │ │ │  
│ │ │ │ -  do {
│ │ │ │ -    if ((i = write(1, buf+wrote, len-wrote)) <= 0)
│ │ │ │ -      return (i);
│ │ │ │ +  do {
│ │ │ │ +    if ((i = write(1, buf+wrote, len-wrote)) <= 0)
│ │ │ │ +      return (i);
│ │ │ │      wrote += i;
│ │ │ │ -  } while (wrote<len);
│ │ │ │ +  } while (wrote<len);
│ │ │ │  
│ │ │ │ -  return (len);
│ │ │ │ -}
│ │ │ │ +  return (len);
│ │ │ │ +}
│ │ │ │  
│ │ │ │ -int read_cmd(byte *buf)
│ │ │ │ -{
│ │ │ │ +int read_cmd(byte *buf)
│ │ │ │ +{
│ │ │ │    int len;
│ │ │ │  
│ │ │ │ -  if (read_exact(buf, 2) != 2)
│ │ │ │ -    return(-1);
│ │ │ │ -  len = (buf[0] << 8) | buf[1];
│ │ │ │ -  return read_exact(buf, len);
│ │ │ │ -}
│ │ │ │ +  if (read_exact(buf, 2) != 2)
│ │ │ │ +    return(-1);
│ │ │ │ +  len = (buf[0] << 8) | buf[1];
│ │ │ │ +  return read_exact(buf, len);
│ │ │ │ +}
│ │ │ │  
│ │ │ │ -int write_cmd(byte *buf, int len)
│ │ │ │ -{
│ │ │ │ +int write_cmd(byte *buf, int len)
│ │ │ │ +{
│ │ │ │    byte li;
│ │ │ │  
│ │ │ │ -  li = (len >> 8) & 0xff;
│ │ │ │ -  write_exact(&li, 1);
│ │ │ │ +  li = (len >> 8) & 0xff;
│ │ │ │ +  write_exact(&li, 1);
│ │ │ │  
│ │ │ │    li = len & 0xff;
│ │ │ │ -  write_exact(&li, 1);
│ │ │ │ +  write_exact(&li, 1);
│ │ │ │  
│ │ │ │ -  return write_exact(buf, len);
│ │ │ │ -}

│ │ │ │ + return write_exact(buf, len); │ │ │ │ +}

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Running the Example │ │ │ │

│ │ │ │

Step 1. Compile the C code. This provides the paths to the include file │ │ │ │ ei.h, and also to the library ei:

$ gcc -o extprg -I/usr/local/otp/lib/erl_interface-3.9.2/include \
│ │ │ │      -L/usr/local/otp/lib/erl_interface-3.9.2/lib \
│ │ │ │      complex.c erl_comm.c ei.c -lei -lpthread

In Erlang/OTP R5B and later versions of OTP, the include and lib directories │ │ │ │ are situated under $OTPROOT/lib/erl_interface-VSN, where $OTPROOT is the │ │ │ │ root directory of the OTP installation (/usr/local/otp in the recent example) │ │ │ │ and VSN is the version of the Erl_interface application (3.2.1 in the recent │ │ │ │ example).

In R4B and earlier versions of OTP, include and lib are situated under │ │ │ │ $OTPROOT/usr.

Step 2. Start Erlang and compile the Erlang code:

$ erl
│ │ │ │ -Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
│ │ │ │ +Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
│ │ │ │  
│ │ │ │ -Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
│ │ │ │ -1> c(complex2).
│ │ │ │ -{ok,complex2}

Step 3. Run the example:

2> complex2:start("./extprg").
│ │ │ │ +Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
│ │ │ │ +1> c(complex2).
│ │ │ │ +{ok,complex2}

Step 3. Run the example:

2> complex2:start("./extprg").
│ │ │ │  <0.34.0>
│ │ │ │ -3> complex2:foo(3).
│ │ │ │ +3> complex2:foo(3).
│ │ │ │  4
│ │ │ │ -4> complex2:bar(5).
│ │ │ │ +4> complex2:bar(5).
│ │ │ │  10
│ │ │ │ -5> complex2:bar(352).
│ │ │ │ +5> complex2:bar(352).
│ │ │ │  704
│ │ │ │ -6> complex2:stop().
│ │ │ │ +6> complex2:stop().
│ │ │ │  stop
│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/eff_guide_processes.xhtml │ │ │ │ @@ -24,45 +24,45 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Creating an Erlang Process │ │ │ │ │ │ │ │

An Erlang process is lightweight compared to threads and processes in operating │ │ │ │ systems.

A newly spawned Erlang process uses 327 words of memory. The size can be found │ │ │ │ -as follows:

Erlang/OTP 27 [erts-14.2.3] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
│ │ │ │ +as follows:

Erlang/OTP 27 [erts-14.2.3] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
│ │ │ │  
│ │ │ │ -Eshell V14.2.3 (press Ctrl+G to abort, type help(). for help)
│ │ │ │ -1> Fun = fun() -> receive after infinity -> ok end end.
│ │ │ │ +Eshell V14.2.3 (press Ctrl+G to abort, type help(). for help)
│ │ │ │ +1> Fun = fun() -> receive after infinity -> ok end end.
│ │ │ │  #Fun<erl_eval.43.39164016>
│ │ │ │ -2> {_,Bytes} = process_info(spawn(Fun), memory).
│ │ │ │ -{memory,2616}
│ │ │ │ -3> Bytes div erlang:system_info(wordsize).
│ │ │ │ +2> {_,Bytes} = process_info(spawn(Fun), memory).
│ │ │ │ +{memory,2616}
│ │ │ │ +3> Bytes div erlang:system_info(wordsize).
│ │ │ │  327

The size includes 233 words for the heap area (which includes the stack). The │ │ │ │ garbage collector increases the heap as needed.

The main (outer) loop for a process must be tail-recursive. Otherwise, the │ │ │ │ -stack grows until the process terminates.

DO NOT

loop() ->
│ │ │ │ +stack grows until the process terminates.

DO NOT

loop() ->
│ │ │ │    receive
│ │ │ │ -     {sys, Msg} ->
│ │ │ │ -         handle_sys_msg(Msg),
│ │ │ │ -         loop();
│ │ │ │ -     {From, Msg} ->
│ │ │ │ -          Reply = handle_msg(Msg),
│ │ │ │ +     {sys, Msg} ->
│ │ │ │ +         handle_sys_msg(Msg),
│ │ │ │ +         loop();
│ │ │ │ +     {From, Msg} ->
│ │ │ │ +          Reply = handle_msg(Msg),
│ │ │ │            From ! Reply,
│ │ │ │ -          loop()
│ │ │ │ +          loop()
│ │ │ │    end,
│ │ │ │ -  io:format("Message is processed~n", []).

The call to io:format/2 will never be executed, but a return address will │ │ │ │ + io:format("Message is processed~n", []).

The call to io:format/2 will never be executed, but a return address will │ │ │ │ still be pushed to the stack each time loop/0 is called recursively. The │ │ │ │ -correct tail-recursive version of the function looks as follows:

DO

loop() ->
│ │ │ │ +correct tail-recursive version of the function looks as follows:

DO

loop() ->
│ │ │ │     receive
│ │ │ │ -      {sys, Msg} ->
│ │ │ │ -         handle_sys_msg(Msg),
│ │ │ │ -         loop();
│ │ │ │ -      {From, Msg} ->
│ │ │ │ -         Reply = handle_msg(Msg),
│ │ │ │ +      {sys, Msg} ->
│ │ │ │ +         handle_sys_msg(Msg),
│ │ │ │ +         loop();
│ │ │ │ +      {From, Msg} ->
│ │ │ │ +         Reply = handle_msg(Msg),
│ │ │ │           From ! Reply,
│ │ │ │ -         loop()
│ │ │ │ +         loop()
│ │ │ │   end.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Initial Heap Size │ │ │ │

│ │ │ │

The default initial heap size of 233 words is quite conservative to support │ │ │ │ @@ -94,30 +94,30 @@ │ │ │ │ │ │ │ │ │ │ │ │ Receiving messages │ │ │ │ │ │ │ │

The cost of receiving messages depends on how complicated the receive │ │ │ │ expression is. A simple expression that matches any message is very cheap │ │ │ │ because it retrieves the first message in the message queue:

DO

receive
│ │ │ │ -    Message -> handle_msg(Message)
│ │ │ │ +    Message -> handle_msg(Message)
│ │ │ │  end.

However, this is not always convenient: we can receive a message that we do not │ │ │ │ know how to handle at this point, so it is common to only match the messages we │ │ │ │ expect:

receive
│ │ │ │ -    {Tag, Message} -> handle_msg(Message)
│ │ │ │ +    {Tag, Message} -> handle_msg(Message)
│ │ │ │  end.

While this is convenient it means that the entire message queue must be searched │ │ │ │ until it finds a matching message. This is very expensive for processes with │ │ │ │ long message queues, so there is an optimization for the common case of │ │ │ │ -sending a request and waiting for a response shortly after:

DO

MRef = monitor(process, Process),
│ │ │ │ -Process ! {self(), MRef, Request},
│ │ │ │ +sending a request and waiting for a response shortly after:

DO

MRef = monitor(process, Process),
│ │ │ │ +Process ! {self(), MRef, Request},
│ │ │ │  receive
│ │ │ │ -    {MRef, Reply} ->
│ │ │ │ -        erlang:demonitor(MRef, [flush]),
│ │ │ │ -        handle_reply(Reply);
│ │ │ │ -    {'DOWN', MRef, _, _, Reason} ->
│ │ │ │ -        handle_error(Reason)
│ │ │ │ +    {MRef, Reply} ->
│ │ │ │ +        erlang:demonitor(MRef, [flush]),
│ │ │ │ +        handle_reply(Reply);
│ │ │ │ +    {'DOWN', MRef, _, _, Reason} ->
│ │ │ │ +        handle_error(Reason)
│ │ │ │  end.

Since the compiler knows that the reference created by │ │ │ │ monitor/2 cannot exist before the call (since it is a globally │ │ │ │ unique identifier), and that the receive only matches messages that contain │ │ │ │ said reference, it will tell the emulator to search only the messages that │ │ │ │ arrived after the call to monitor/2.

The above is a simple example where one is but guaranteed that the optimization │ │ │ │ will take, but what about more complicated code?

│ │ │ │ │ │ │ │ @@ -133,101 +133,101 @@ │ │ │ │ efficiency_guide.erl:200: Warning: NOT OPTIMIZED: all clauses do not match a suitable reference │ │ │ │ efficiency_guide.erl:206: Warning: OPTIMIZED: reference used to mark a message queue position │ │ │ │ efficiency_guide.erl:208: Warning: OPTIMIZED: all clauses match reference created by monitor/2 at efficiency_guide.erl:206 │ │ │ │ efficiency_guide.erl:219: Warning: INFO: passing reference created by make_ref/0 at efficiency_guide.erl:218 │ │ │ │ efficiency_guide.erl:222: Warning: OPTIMIZED: all clauses match reference in function parameter 1

To make it clearer exactly what code the warnings refer to, the warnings in the │ │ │ │ following examples are inserted as comments after the clause they refer to, for │ │ │ │ example:

%% DO
│ │ │ │ -simple_receive() ->
│ │ │ │ +simple_receive() ->
│ │ │ │  %% efficiency_guide.erl:194: Warning: INFO: not a selective receive, this is always fast
│ │ │ │  receive
│ │ │ │ -    Message -> handle_msg(Message)
│ │ │ │ +    Message -> handle_msg(Message)
│ │ │ │  end.
│ │ │ │  
│ │ │ │  %% DO NOT, unless Tag is known to be a suitable reference: see
│ │ │ │  %% cross_function_receive/0 further down.
│ │ │ │ -selective_receive(Tag, Message) ->
│ │ │ │ +selective_receive(Tag, Message) ->
│ │ │ │  %% efficiency_guide.erl:200: Warning: NOT OPTIMIZED: all clauses do not match a suitable reference
│ │ │ │  receive
│ │ │ │ -    {Tag, Message} -> handle_msg(Message)
│ │ │ │ +    {Tag, Message} -> handle_msg(Message)
│ │ │ │  end.
│ │ │ │  
│ │ │ │  %% DO
│ │ │ │ -optimized_receive(Process, Request) ->
│ │ │ │ +optimized_receive(Process, Request) ->
│ │ │ │  %% efficiency_guide.erl:206: Warning: OPTIMIZED: reference used to mark a message queue position
│ │ │ │ -    MRef = monitor(process, Process),
│ │ │ │ -    Process ! {self(), MRef, Request},
│ │ │ │ +    MRef = monitor(process, Process),
│ │ │ │ +    Process ! {self(), MRef, Request},
│ │ │ │      %% efficiency_guide.erl:208: Warning: OPTIMIZED: matches reference created by monitor/2 at efficiency_guide.erl:206
│ │ │ │      receive
│ │ │ │ -        {MRef, Reply} ->
│ │ │ │ -        erlang:demonitor(MRef, [flush]),
│ │ │ │ -        handle_reply(Reply);
│ │ │ │ -    {'DOWN', MRef, _, _, Reason} ->
│ │ │ │ -    handle_error(Reason)
│ │ │ │ +        {MRef, Reply} ->
│ │ │ │ +        erlang:demonitor(MRef, [flush]),
│ │ │ │ +        handle_reply(Reply);
│ │ │ │ +    {'DOWN', MRef, _, _, Reason} ->
│ │ │ │ +    handle_error(Reason)
│ │ │ │      end.
│ │ │ │  
│ │ │ │  %% DO
│ │ │ │ -cross_function_receive() ->
│ │ │ │ +cross_function_receive() ->
│ │ │ │      %% efficiency_guide.erl:218: Warning: OPTIMIZED: reference used to mark a message queue position
│ │ │ │ -    Ref = make_ref(),
│ │ │ │ +    Ref = make_ref(),
│ │ │ │      %% efficiency_guide.erl:219: Warning: INFO: passing reference created by make_ref/0 at efficiency_guide.erl:218
│ │ │ │ -    cross_function_receive(Ref).
│ │ │ │ +    cross_function_receive(Ref).
│ │ │ │  
│ │ │ │ -cross_function_receive(Ref) ->
│ │ │ │ +cross_function_receive(Ref) ->
│ │ │ │      %% efficiency_guide.erl:222: Warning: OPTIMIZED: all clauses match reference in function parameter 1
│ │ │ │      receive
│ │ │ │ -        {Ref, Message} -> handle_msg(Message)
│ │ │ │ +        {Ref, Message} -> handle_msg(Message)
│ │ │ │      end.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Literal Pool │ │ │ │

│ │ │ │

Constant Erlang terms (hereafter called literals) are kept in literal pools; │ │ │ │ each loaded module has its own pool. The following function does not build the │ │ │ │ tuple every time it is called (only to have it discarded the next time the │ │ │ │ garbage collector was run), but the tuple is located in the module's literal │ │ │ │ -pool:

DO

days_in_month(M) ->
│ │ │ │ -    element(M, {31,28,31,30,31,30,31,31,30,31,30,31}).

If a literal, or a term that contains a literal, is inserted into an Ets table, │ │ │ │ +pool:

DO

days_in_month(M) ->
│ │ │ │ +    element(M, {31,28,31,30,31,30,31,31,30,31,30,31}).

If a literal, or a term that contains a literal, is inserted into an Ets table, │ │ │ │ it is copied. The reason is that the module containing the literal can be │ │ │ │ unloaded in the future.

When a literal is sent to another process, it is not copied. When a module │ │ │ │ holding a literal is unloaded, the literal will be copied to the heap of all │ │ │ │ processes that hold references to that literal.

There also exists a global literal pool that is managed by the │ │ │ │ persistent_term module.

By default, 1 GB of virtual address space is reserved for all literal pools (in │ │ │ │ BEAM code and persistent terms). The amount of virtual address space reserved │ │ │ │ for literals can be changed by using the │ │ │ │ +MIscs option when starting the emulator.

Here is an example how the reserved virtual address space for literals can be │ │ │ │ raised to 2 GB (2048 MB):

erl +MIscs 2048

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Loss of Sharing │ │ │ │

│ │ │ │ -

An Erlang term can have shared subterms. Here is a simple example:

{SubTerm, SubTerm}

Shared subterms are not preserved in the following cases:

  • When a term is sent to another process
  • When a term is passed as the initial process arguments in the spawn call
  • When a term is stored in an Ets table

That is an optimization. Most applications do not send messages with shared │ │ │ │ -subterms.

The following example shows how a shared subterm can be created:

kilo_byte() ->
│ │ │ │ -    kilo_byte(10, [42]).
│ │ │ │ +

An Erlang term can have shared subterms. Here is a simple example:

{SubTerm, SubTerm}

Shared subterms are not preserved in the following cases:

  • When a term is sent to another process
  • When a term is passed as the initial process arguments in the spawn call
  • When a term is stored in an Ets table

That is an optimization. Most applications do not send messages with shared │ │ │ │ +subterms.

The following example shows how a shared subterm can be created:

kilo_byte() ->
│ │ │ │ +    kilo_byte(10, [42]).
│ │ │ │  
│ │ │ │ -kilo_byte(0, Acc) ->
│ │ │ │ +kilo_byte(0, Acc) ->
│ │ │ │      Acc;
│ │ │ │ -kilo_byte(N, Acc) ->
│ │ │ │ -    kilo_byte(N-1, [Acc|Acc]).

kilo_byte/1 creates a deep list. If list_to_binary/1 │ │ │ │ +kilo_byte(N, Acc) -> │ │ │ │ + kilo_byte(N-1, [Acc|Acc]).

kilo_byte/1 creates a deep list. If list_to_binary/1 │ │ │ │ is called, the deep list can be converted to a binary of 1024 bytes:

1> byte_size(list_to_binary(efficiency_guide:kilo_byte())).
│ │ │ │  1024

Using the erts_debug:size/1 BIF, it can be seen that the deep list only │ │ │ │ -requires 22 words of heap space:

2> erts_debug:size(efficiency_guide:kilo_byte()).
│ │ │ │ +requires 22 words of heap space:

2> erts_debug:size(efficiency_guide:kilo_byte()).
│ │ │ │  22

Using the erts_debug:flat_size/1 BIF, the size of the deep list can be │ │ │ │ calculated if sharing is ignored. It becomes the size of the list when it has │ │ │ │ -been sent to another process or stored in an Ets table:

3> erts_debug:flat_size(efficiency_guide:kilo_byte()).
│ │ │ │ +been sent to another process or stored in an Ets table:

3> erts_debug:flat_size(efficiency_guide:kilo_byte()).
│ │ │ │  4094

It can be verified that sharing will be lost if the data is inserted into an Ets │ │ │ │ -table:

4> T = ets:new(tab, []).
│ │ │ │ +table:

4> T = ets:new(tab, []).
│ │ │ │  #Ref<0.1662103692.2407923716.214181>
│ │ │ │ -5> ets:insert(T, {key,efficiency_guide:kilo_byte()}).
│ │ │ │ +5> ets:insert(T, {key,efficiency_guide:kilo_byte()}).
│ │ │ │  true
│ │ │ │ -6> erts_debug:size(element(2, hd(ets:lookup(T, key)))).
│ │ │ │ +6> erts_debug:size(element(2, hd(ets:lookup(T, key)))).
│ │ │ │  4094
│ │ │ │ -7> erts_debug:flat_size(element(2, hd(ets:lookup(T, key)))).
│ │ │ │ +7> erts_debug:flat_size(element(2, hd(ets:lookup(T, key)))).
│ │ │ │  4094

When the data has passed through an Ets table, erts_debug:size/1 and │ │ │ │ erts_debug:flat_size/1 return the same value. Sharing has been lost.

It is possible to build an experimental variant of the runtime system that │ │ │ │ will preserve sharing when copying terms by giving the │ │ │ │ --enable-sharing-preserving option to the configure script.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/eff_guide_functions.xhtml │ │ │ │ @@ -27,67 +27,67 @@ │ │ │ │ Pattern Matching │ │ │ │

│ │ │ │

Pattern matching in function head as well as in case and receive clauses are │ │ │ │ optimized by the compiler. With a few exceptions, there is nothing to gain by │ │ │ │ rearranging clauses.

One exception is pattern matching of binaries. The compiler does not rearrange │ │ │ │ clauses that match binaries. Placing the clause that matches against the empty │ │ │ │ binary last is usually slightly faster than placing it first.

The following is a rather unnatural example to show another exception where │ │ │ │ -rearranging clauses is beneficial:

DO NOT

atom_map1(one) -> 1;
│ │ │ │ -atom_map1(two) -> 2;
│ │ │ │ -atom_map1(three) -> 3;
│ │ │ │ -atom_map1(Int) when is_integer(Int) -> Int;
│ │ │ │ -atom_map1(four) -> 4;
│ │ │ │ -atom_map1(five) -> 5;
│ │ │ │ -atom_map1(six) -> 6.

The problem is the clause with the variable Int. As a variable can match │ │ │ │ +rearranging clauses is beneficial:

DO NOT

atom_map1(one) -> 1;
│ │ │ │ +atom_map1(two) -> 2;
│ │ │ │ +atom_map1(three) -> 3;
│ │ │ │ +atom_map1(Int) when is_integer(Int) -> Int;
│ │ │ │ +atom_map1(four) -> 4;
│ │ │ │ +atom_map1(five) -> 5;
│ │ │ │ +atom_map1(six) -> 6.

The problem is the clause with the variable Int. As a variable can match │ │ │ │ anything, including the atoms four, five, and six, which the following │ │ │ │ clauses also match, the compiler must generate suboptimal code that executes as │ │ │ │ follows:

  • First, the input value is compared to one, two, and three (using a │ │ │ │ single instruction that does a binary search; thus, quite efficient even if │ │ │ │ there are many values) to select which one of the first three clauses to │ │ │ │ execute (if any).
  • If none of the first three clauses match, the fourth clause match as a │ │ │ │ variable always matches.
  • If the guard test is_integer(Int) succeeds, the fourth │ │ │ │ clause is executed.
  • If the guard test fails, the input value is compared to four, five, and │ │ │ │ six, and the appropriate clause is selected. (There is a function_clause │ │ │ │ -exception if none of the values matched.)

Rewriting to either:

DO

atom_map2(one) -> 1;
│ │ │ │ -atom_map2(two) -> 2;
│ │ │ │ -atom_map2(three) -> 3;
│ │ │ │ -atom_map2(four) -> 4;
│ │ │ │ -atom_map2(five) -> 5;
│ │ │ │ -atom_map2(six) -> 6;
│ │ │ │ -atom_map2(Int) when is_integer(Int) -> Int.

or:

DO

atom_map3(Int) when is_integer(Int) -> Int;
│ │ │ │ -atom_map3(one) -> 1;
│ │ │ │ -atom_map3(two) -> 2;
│ │ │ │ -atom_map3(three) -> 3;
│ │ │ │ -atom_map3(four) -> 4;
│ │ │ │ -atom_map3(five) -> 5;
│ │ │ │ -atom_map3(six) -> 6.

gives slightly more efficient matching code.

Another example:

DO NOT

map_pairs1(_Map, [], Ys) ->
│ │ │ │ +exception if none of the values matched.)

Rewriting to either:

DO

atom_map2(one) -> 1;
│ │ │ │ +atom_map2(two) -> 2;
│ │ │ │ +atom_map2(three) -> 3;
│ │ │ │ +atom_map2(four) -> 4;
│ │ │ │ +atom_map2(five) -> 5;
│ │ │ │ +atom_map2(six) -> 6;
│ │ │ │ +atom_map2(Int) when is_integer(Int) -> Int.

or:

DO

atom_map3(Int) when is_integer(Int) -> Int;
│ │ │ │ +atom_map3(one) -> 1;
│ │ │ │ +atom_map3(two) -> 2;
│ │ │ │ +atom_map3(three) -> 3;
│ │ │ │ +atom_map3(four) -> 4;
│ │ │ │ +atom_map3(five) -> 5;
│ │ │ │ +atom_map3(six) -> 6.

gives slightly more efficient matching code.

Another example:

DO NOT

map_pairs1(_Map, [], Ys) ->
│ │ │ │      Ys;
│ │ │ │ -map_pairs1(_Map, Xs, []) ->
│ │ │ │ +map_pairs1(_Map, Xs, []) ->
│ │ │ │      Xs;
│ │ │ │ -map_pairs1(Map, [X|Xs], [Y|Ys]) ->
│ │ │ │ -    [Map(X, Y)|map_pairs1(Map, Xs, Ys)].

The first argument is not a problem. It is variable, but it is a variable in │ │ │ │ +map_pairs1(Map, [X|Xs], [Y|Ys]) -> │ │ │ │ + [Map(X, Y)|map_pairs1(Map, Xs, Ys)].

The first argument is not a problem. It is variable, but it is a variable in │ │ │ │ all clauses. The problem is the variable in the second argument, Xs, in the │ │ │ │ middle clause. Because the variable can match anything, the compiler is not │ │ │ │ allowed to rearrange the clauses, but must generate code that matches them in │ │ │ │ the order written.

If the function is rewritten as follows, the compiler is free to rearrange the │ │ │ │ -clauses:

DO

map_pairs2(_Map, [], Ys) ->
│ │ │ │ +clauses:

DO

map_pairs2(_Map, [], Ys) ->
│ │ │ │      Ys;
│ │ │ │ -map_pairs2(_Map, [_|_]=Xs, [] ) ->
│ │ │ │ +map_pairs2(_Map, [_|_]=Xs, [] ) ->
│ │ │ │      Xs;
│ │ │ │ -map_pairs2(Map, [X|Xs], [Y|Ys]) ->
│ │ │ │ -    [Map(X, Y)|map_pairs2(Map, Xs, Ys)].

The compiler will generate code similar to this:

DO NOT (already done by the compiler)

explicit_map_pairs(Map, Xs0, Ys0) ->
│ │ │ │ +map_pairs2(Map, [X|Xs], [Y|Ys]) ->
│ │ │ │ +    [Map(X, Y)|map_pairs2(Map, Xs, Ys)].

The compiler will generate code similar to this:

DO NOT (already done by the compiler)

explicit_map_pairs(Map, Xs0, Ys0) ->
│ │ │ │      case Xs0 of
│ │ │ │ -	[X|Xs] ->
│ │ │ │ +	[X|Xs] ->
│ │ │ │  	    case Ys0 of
│ │ │ │ -		[Y|Ys] ->
│ │ │ │ -		    [Map(X, Y)|explicit_map_pairs(Map, Xs, Ys)];
│ │ │ │ -		[] ->
│ │ │ │ +		[Y|Ys] ->
│ │ │ │ +		    [Map(X, Y)|explicit_map_pairs(Map, Xs, Ys)];
│ │ │ │ +		[] ->
│ │ │ │  		    Xs0
│ │ │ │  	    end;
│ │ │ │ -	[] ->
│ │ │ │ +	[] ->
│ │ │ │  	    Ys0
│ │ │ │      end.

This is slightly faster for probably the most common case that the input lists │ │ │ │ are not empty or very short. (Another advantage is that Dialyzer can deduce a │ │ │ │ better type for the Xs variable.)

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/drivers.xhtml │ │ │ │ @@ -27,23 +27,23 @@ │ │ │ │ Drivers and Concurrency │ │ │ │

│ │ │ │

The runtime system always takes a lock before running any code in a driver.

By default, that lock is at the driver level, that is, if several ports have │ │ │ │ been opened to the same driver, only code for one port at the same time can be │ │ │ │ running.

A driver can be configured to have one lock for each port instead.

If a driver is used in a functional way (that is, holds no state, but only does │ │ │ │ some heavy calculation and returns a result), several ports with registered │ │ │ │ names can be opened beforehand, and the port to be used can be chosen based on │ │ │ │ -the scheduler ID as follows:

-define(PORT_NAMES(),
│ │ │ │ -	{some_driver_01, some_driver_02, some_driver_03, some_driver_04,
│ │ │ │ +the scheduler ID as follows:

-define(PORT_NAMES(),
│ │ │ │ +	{some_driver_01, some_driver_02, some_driver_03, some_driver_04,
│ │ │ │  	 some_driver_05, some_driver_06, some_driver_07, some_driver_08,
│ │ │ │  	 some_driver_09, some_driver_10, some_driver_11, some_driver_12,
│ │ │ │ -	 some_driver_13, some_driver_14, some_driver_15, some_driver_16}).
│ │ │ │ +	 some_driver_13, some_driver_14, some_driver_15, some_driver_16}).
│ │ │ │  
│ │ │ │ -client_port() ->
│ │ │ │ -    element(erlang:system_info(scheduler_id) rem tuple_size(?PORT_NAMES()) + 1,
│ │ │ │ -	    ?PORT_NAMES()).

As long as there are no more than 16 schedulers, there will never be any lock │ │ │ │ +client_port() -> │ │ │ │ + element(erlang:system_info(scheduler_id) rem tuple_size(?PORT_NAMES()) + 1, │ │ │ │ + ?PORT_NAMES()).

As long as there are no more than 16 schedulers, there will never be any lock │ │ │ │ contention on the port lock for the driver.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Avoiding Copying Binaries When Calling a Driver │ │ │ │

│ │ │ │

There are basically two ways to avoid copying a binary that is sent to a driver:

  • If the Data argument for port_control/3 is a │ │ │ ├── OEBPS/documentation.xhtml │ │ │ │ @@ -17,23 +17,23 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │

    │ │ │ │ Documentation │ │ │ │

    │ │ │ │

    Documentation in Erlang is done through the -moduledoc and -doc │ │ │ │ -attributes. For example:

    -module(arith).
    │ │ │ │ +attributes. For example:

    -module(arith).
    │ │ │ │  -moduledoc """
    │ │ │ │  A module for basic arithmetic.
    │ │ │ │  """.
    │ │ │ │  
    │ │ │ │ --export([add/2]).
    │ │ │ │ +-export([add/2]).
    │ │ │ │  
    │ │ │ │  -doc "Adds two numbers.".
    │ │ │ │ -add(One, Two) -> One + Two.

    The -moduledoc attribute has to be located before the first -doc attribute │ │ │ │ +add(One, Two) -> One + Two.

    The -moduledoc attribute has to be located before the first -doc attribute │ │ │ │ or function declaration. It documents the overall purpose of the module.

    The -doc attribute always precedes the function or │ │ │ │ attribute it documents. The │ │ │ │ attributes that can be documented are │ │ │ │ user-defined types │ │ │ │ (-type and -opaque) and │ │ │ │ behaviour module attributes │ │ │ │ (-callback).

    By default the format used for documentation attributes is │ │ │ │ @@ -45,55 +45,55 @@ │ │ │ │ Documentation Attributes.

    -doc attributes have been available since Erlang/OTP 27.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Documentation metadata │ │ │ │

    │ │ │ │

    It is possible to add metadata to the documentation entry. You do this by adding │ │ │ │ -a -moduledoc or -doc attribute with a map as argument. For example:

    -module(arith).
    │ │ │ │ +a -moduledoc or -doc attribute with a map as argument. For example:

    -module(arith).
    │ │ │ │  -moduledoc """
    │ │ │ │  A module for basic arithmetic.
    │ │ │ │  """.
    │ │ │ │ --moduledoc #{since => "1.0"}.
    │ │ │ │ +-moduledoc #{since => "1.0"}.
    │ │ │ │  
    │ │ │ │ --export([add/2]).
    │ │ │ │ +-export([add/2]).
    │ │ │ │  
    │ │ │ │  -doc "Adds two numbers.".
    │ │ │ │ --doc(#{since => "1.0"}).
    │ │ │ │ -add(One, Two) -> One + Two.

    The metadata is used by documentation tools to provide extra information to the │ │ │ │ +-doc(#{since => "1.0"}). │ │ │ │ +add(One, Two) -> One + Two.

    The metadata is used by documentation tools to provide extra information to the │ │ │ │ user. There can be multiple metadata documentation entries, in which case the │ │ │ │ maps will be merged with the latest taking precedence if there are duplicate │ │ │ │ keys. Example:

    -doc "Adds two numbers.".
    │ │ │ │ --doc #{since => "1.0", author => "Joe"}.
    │ │ │ │ --doc #{since => "2.0"}.
    │ │ │ │ -add(One, Two) -> One + Two.

    This will result in a metadata entry of #{since => "2.0", author => "Joe"}.

    The keys and values in the metadata map can be any type, but it is recommended │ │ │ │ +-doc #{since => "1.0", author => "Joe"}. │ │ │ │ +-doc #{since => "2.0"}. │ │ │ │ +add(One, Two) -> One + Two.

This will result in a metadata entry of #{since => "2.0", author => "Joe"}.

The keys and values in the metadata map can be any type, but it is recommended │ │ │ │ that only atoms are used for keys and │ │ │ │ strings for the values.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ External documentation files │ │ │ │

│ │ │ │

The -moduledoc and -doc can also be placed in external files. To do so use │ │ │ │ -doc {file, "path/to/doc.md"} to point to the documentation. The path used is │ │ │ │ relative to the file where the -doc attribute is located. For example:

%% doc/add.md
│ │ │ │  Adds two numbers.

and

%% src/arith.erl
│ │ │ │ --doc({file, "../doc/add.md"}).
│ │ │ │ -add(One, Two) -> One + Two.

│ │ │ │ +-doc({file, "../doc/add.md"}). │ │ │ │ +add(One, Two) -> One + Two.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Documenting a module │ │ │ │

│ │ │ │

The module description should include details on how to use the API and examples │ │ │ │ of the different functions working together. Here is a good place to use images │ │ │ │ and other diagrams to better show the usage of the module. Instead of writing a │ │ │ │ long text in the moduledoc attribute, it could be better to break it out into │ │ │ │ an external page.

The moduledoc attribute should start with a short paragraph describing the │ │ │ │ -module and then go into greater details. For example:

-module(arith).
│ │ │ │ +module and then go into greater details. For example:

-module(arith).
│ │ │ │  -moduledoc """
│ │ │ │     A module for basic arithmetic.
│ │ │ │  
│ │ │ │     This module can be used to add and subtract values. For example:
│ │ │ │  
│ │ │ │     ```erlang
│ │ │ │     1> arith:substract(arith:add(2, 3), 1).
│ │ │ │ @@ -108,94 +108,94 @@
│ │ │ │  

There are three reserved metadata keys for -moduledoc:

  • since - Shows in which version of the application the module was added. │ │ │ │ If this is added, all functions, types, and callbacks within will also receive │ │ │ │ the same since value unless specified in the metadata of the function, type │ │ │ │ or callback.
  • deprecated - Shows a text in the documentation explaining that it is │ │ │ │ deprecated and what to use instead.
  • format - The format to use for all documentation in this module. The │ │ │ │ default is text/markdown. It should be written using the │ │ │ │ mime type │ │ │ │ -of the format.

Example:

-moduledoc {file, "../doc/arith.asciidoc"}.
│ │ │ │ --moduledoc #{since => "0.1", format => "text/asciidoc"}.
│ │ │ │ --moduledoc #{deprecated => "Use the Erlang arithmetic operators instead."}.

│ │ │ │ +of the format.

Example:

-moduledoc {file, "../doc/arith.asciidoc"}.
│ │ │ │ +-moduledoc #{since => "0.1", format => "text/asciidoc"}.
│ │ │ │ +-moduledoc #{deprecated => "Use the Erlang arithmetic operators instead."}.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Documenting functions, user-defined types, and callbacks │ │ │ │

│ │ │ │

Functions, types, and callbacks can be documented using the -doc attribute. │ │ │ │ Each entry should start with a short paragraph describing the purpose of entity, │ │ │ │ and then go into greater detail in needed.

It is not recommended to include images or diagrams in this documentation as it │ │ │ │ is used by IDEs and c:h/1 to show the documentation to the user.

For example:

-doc """
│ │ │ │  A number that can be used by the arith module.
│ │ │ │  
│ │ │ │  We use a special number here so that we know
│ │ │ │  that this number comes from this module.
│ │ │ │  """.
│ │ │ │ --opaque number() :: {arith, erlang:number()}.
│ │ │ │ +-opaque number() :: {arith, erlang:number()}.
│ │ │ │  
│ │ │ │  -doc """
│ │ │ │  Adds two numbers.
│ │ │ │  
│ │ │ │  ### Example:
│ │ │ │  
│ │ │ │  ```
│ │ │ │  1> arith:add(arith:number(1), arith:number(2)). {number, 3}
│ │ │ │  ```
│ │ │ │  """.
│ │ │ │ --spec add(number(), number()) -> number().
│ │ │ │ -add({number, One}, {number, Two}) -> {number, One + Two}.

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

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

│ │ │ │

There are four reserved metadata keys for -doc:

  • since => unicode:chardata() - Shows which version of the application the │ │ │ │ module was added.

  • deprecated => unicode:chardata() - Shows a text in the documentation │ │ │ │ explaining that it is deprecated and what to use instead. The compiler will │ │ │ │ automatically insert this key if there is a -deprecated attribute marking a │ │ │ │ function as deprecated.

  • equiv => unicode:chardata() | F/A | F(...) - Notes that this function is equivalent to │ │ │ │ another function in this module. The equivalence can be described using either │ │ │ │ -Func/Arity, Func(Args) or a unicode string. For example:

    -doc #{equiv => add/3}.
    │ │ │ │ -add(One, Two) -> add(One, Two, []).
    │ │ │ │ -add(One, Two, Options) -> ...

    or

    -doc #{equiv => add(One, Two, [])}.
    │ │ │ │ --spec add(One :: number(), Two :: number()) -> number().
    │ │ │ │ -add(One, Two) -> add(One, Two, []).
    │ │ │ │ -add(One, Two, Options) -> ...

    The entry into the EEP-48 doc chunk metadata is │ │ │ │ +Func/Arity, Func(Args) or a unicode string. For example:

    -doc #{equiv => add/3}.
    │ │ │ │ +add(One, Two) -> add(One, Two, []).
    │ │ │ │ +add(One, Two, Options) -> ...

    or

    -doc #{equiv => add(One, Two, [])}.
    │ │ │ │ +-spec add(One :: number(), Two :: number()) -> number().
    │ │ │ │ +add(One, Two) -> add(One, Two, []).
    │ │ │ │ +add(One, Two, Options) -> ...

    The entry into the EEP-48 doc chunk metadata is │ │ │ │ the value converted to a string.

  • exported => boolean() - A boolean/0 signifying if the entry is exported │ │ │ │ or not. This value is automatically set by the compiler and should not be set │ │ │ │ by the user.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Doc signatures │ │ │ │

│ │ │ │

The doc signature is a short text shown to describe the function and its arguments. │ │ │ │ By default it is determined by looking at the names of the arguments in the │ │ │ │ --spec or function. For example:

add(One, Two) -> One + Two.
│ │ │ │ +-spec or function. For example:

add(One, Two) -> One + Two.
│ │ │ │  
│ │ │ │ --spec sub(One :: integer(), Two :: integer()) -> integer().
│ │ │ │ -sub(X, Y) -> X - Y.

will have a signature of add(One, Two) and sub(One, Two).

For types or callbacks, the signature is derived from the type or callback │ │ │ │ -specification. For example:

-type number(Value) :: {number, Value}.
│ │ │ │ +-spec sub(One :: integer(), Two :: integer()) -> integer().
│ │ │ │ +sub(X, Y) -> X - Y.

will have a signature of add(One, Two) and sub(One, Two).

For types or callbacks, the signature is derived from the type or callback │ │ │ │ +specification. For example:

-type number(Value) :: {number, Value}.
│ │ │ │  %% signature will be `number(Value)`
│ │ │ │  
│ │ │ │ --opaque number() :: {number, number()}.
│ │ │ │ +-opaque number() :: {number, number()}.
│ │ │ │  %% signature will be `number()`
│ │ │ │  
│ │ │ │ --callback increment(In :: number()) -> Out.
│ │ │ │ +-callback increment(In :: number()) -> Out.
│ │ │ │  %% signature will be `increment(In)`
│ │ │ │  
│ │ │ │ --callback increment(In) -> Out when In :: number().
│ │ │ │ +-callback increment(In) -> Out when In :: number().
│ │ │ │  %% signature will be `increment(In)`

If it is not possible to "easily" figure out a nice signature from the code, the │ │ │ │ MFA syntax is used instead. For example: add/2, number/1, increment/1

It is possible to supply a custom signature by placing it as the first line of the │ │ │ │ -doc attribute. The provided signature must be in the form of a function │ │ │ │ declaration up until the ->. For example:

-doc """
│ │ │ │  add(One, Two)
│ │ │ │  
│ │ │ │  Adds two numbers.
│ │ │ │  """.
│ │ │ │ -add(A, B) -> A + B.

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

Will create the signature add(One, Two). The signature will be removed from the │ │ │ │ documentation string, so in the example above only the text "Adds two numbers" │ │ │ │ will be part of the documentation. This works for functions, types, and │ │ │ │ callbacks.

│ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Compiling and getting documentation │ │ │ │ @@ -280,21 +280,21 @@ │ │ │ │ Using ExDoc to generate HTML/ePub documentation │ │ │ │

│ │ │ │

ExDoc has built-in support to generate │ │ │ │ documentation from Markdown. The simplest way is by using the │ │ │ │ rebar3_ex_doc plugin. To set up a │ │ │ │ rebar3 project to use ExDoc to generate │ │ │ │ documentation add the following to your rebar3.config.

%% Enable the plugin
│ │ │ │ -{plugins, [rebar3_ex_doc]}.
│ │ │ │ +{plugins, [rebar3_ex_doc]}.
│ │ │ │  
│ │ │ │ -{ex_doc, [
│ │ │ │ -  {extras, ["README.md"]},
│ │ │ │ -  {main, "README.md"},
│ │ │ │ -  {source_url, "https://github.com/namespace/your_app"}
│ │ │ │ -]}.

When configured you can run rebar3 ex_doc to generate the │ │ │ │ +{ex_doc, [ │ │ │ │ + {extras, ["README.md"]}, │ │ │ │ + {main, "README.md"}, │ │ │ │ + {source_url, "https://github.com/namespace/your_app"} │ │ │ │ +]}.

When configured you can run rebar3 ex_doc to generate the │ │ │ │ documentation to doc/index.html. For more details and options see │ │ │ │ the rebar3_ex_doc documentation.

You can also download the │ │ │ │ release escript bundle from │ │ │ │ github and run it from the command line. The documentation for using the escript │ │ │ │ is found by running ex_doc --help.

If you are writing documentation that will be using │ │ │ │ ExDoc to generate HTML/ePub it is highly │ │ │ │ recommended to read its documentation.

│ │ │ ├── OEBPS/distributed_applications.xhtml │ │ │ │ @@ -55,36 +55,36 @@ │ │ │ │ (within the time-out specified by sync_nodes_timeout).
  • sync_nodes_timeout = integer() | infinity - Specifies how many milliseconds │ │ │ │ to wait for the other nodes to start.

  • When started, the node waits for all nodes specified by sync_nodes_mandatory │ │ │ │ and sync_nodes_optional to come up. When all nodes are up, or when all │ │ │ │ mandatory nodes are up and the time specified by sync_nodes_timeout has │ │ │ │ elapsed, all applications start. If not all mandatory nodes are up, the node │ │ │ │ terminates.

    Example:

    An application myapp is to run at the node cp1@cave. If this node goes down, │ │ │ │ myapp is to be restarted at cp2@cave or cp3@cave. A system configuration │ │ │ │ -file cp1.config for cp1@cave can look as follows:

    [{kernel,
    │ │ │ │ -  [{distributed, [{myapp, 5000, [cp1@cave, {cp2@cave, cp3@cave}]}]},
    │ │ │ │ -   {sync_nodes_mandatory, [cp2@cave, cp3@cave]},
    │ │ │ │ -   {sync_nodes_timeout, 5000}
    │ │ │ │ -  ]
    │ │ │ │ - }
    │ │ │ │ -].

    The system configuration files for cp2@cave and cp3@cave are identical, │ │ │ │ +file cp1.config for cp1@cave can look as follows:

    [{kernel,
    │ │ │ │ +  [{distributed, [{myapp, 5000, [cp1@cave, {cp2@cave, cp3@cave}]}]},
    │ │ │ │ +   {sync_nodes_mandatory, [cp2@cave, cp3@cave]},
    │ │ │ │ +   {sync_nodes_timeout, 5000}
    │ │ │ │ +  ]
    │ │ │ │ + }
    │ │ │ │ +].

    The system configuration files for cp2@cave and cp3@cave are identical, │ │ │ │ except for the list of mandatory nodes, which is to be [cp1@cave, cp3@cave] │ │ │ │ for cp2@cave and [cp1@cave, cp2@cave] for cp3@cave.

    Note

    All involved nodes must have the same value for distributed and │ │ │ │ sync_nodes_timeout. Otherwise the system behavior is undefined.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Starting and Stopping Distributed Applications │ │ │ │

    │ │ │ │

    When all involved (mandatory) nodes have been started, the distributed │ │ │ │ application can be started by calling application:start(Application) at all │ │ │ │ of these nodes.

    A boot script (see Releases) can be used that │ │ │ │ automatically starts the application.

    The application is started at the first operational node that is listed in the │ │ │ │ list of nodes in the distributed configuration parameter. The application is │ │ │ │ started as usual. That is, an application master is created and calls the │ │ │ │ -application callback function:

    Module:start(normal, StartArgs)

    Example:

    Continuing the example from the previous section, the three nodes are started, │ │ │ │ +application callback function:

    Module:start(normal, StartArgs)

    Example:

    Continuing the example from the previous section, the three nodes are started, │ │ │ │ specifying the system configuration file:

    > erl -sname cp1 -config cp1
    │ │ │ │  > erl -sname cp2 -config cp2
    │ │ │ │  > erl -sname cp3 -config cp3

    When all nodes are operational, myapp can be started. This is achieved by │ │ │ │ calling application:start(myapp) at all three nodes. It is then started at │ │ │ │ cp1, as shown in the following figure:

    Application myapp - Situation 1

    Similarly, the application must be stopped by calling │ │ │ │ application:stop(Application) at all involved nodes.

    │ │ │ │ │ │ │ │ @@ -92,30 +92,30 @@ │ │ │ │ │ │ │ │ Failover │ │ │ │

    │ │ │ │

    If the node where the application is running goes down, the application is │ │ │ │ restarted (after the specified time-out) at the first operational node that is │ │ │ │ listed in the list of nodes in the distributed configuration parameter. This │ │ │ │ is called a failover.

    The application is started the normal way at the new node, that is, by the │ │ │ │ -application master calling:

    Module:start(normal, StartArgs)

    An exception is if the application has the start_phases key defined (see │ │ │ │ +application master calling:

    Module:start(normal, StartArgs)

    An exception is if the application has the start_phases key defined (see │ │ │ │ Included Applications). The application is then │ │ │ │ -instead started by calling:

    Module:start({failover, Node}, StartArgs)

    Here Node is the terminated node.

    Example:

    If cp1 goes down, the system checks which one of the other nodes, cp2 or │ │ │ │ +instead started by calling:

    Module:start({failover, Node}, StartArgs)

    Here Node is the terminated node.

    Example:

    If cp1 goes down, the system checks which one of the other nodes, cp2 or │ │ │ │ cp3, has the least number of running applications, but waits for 5 seconds for │ │ │ │ cp1 to restart. If cp1 does not restart and cp2 runs fewer applications │ │ │ │ than cp3, myapp is restarted on cp2.

    Application myapp - Situation 2

    Suppose now that cp2 goes also down and does not restart within 5 seconds. │ │ │ │ myapp is now restarted on cp3.

    Application myapp - Situation 3

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Takeover │ │ │ │

    │ │ │ │

    If a node is started, which has higher priority according to distributed than │ │ │ │ the node where a distributed application is running, the application is │ │ │ │ restarted at the new node and stopped at the old node. This is called a │ │ │ │ -takeover.

    The application is started by the application master calling:

    Module:start({takeover, Node}, StartArgs)

    Here Node is the old node.

    Example:

    If myapp is running at cp3, and if cp2 now restarts, it does not restart │ │ │ │ +takeover.

    The application is started by the application master calling:

    Module:start({takeover, Node}, StartArgs)

    Here Node is the old node.

    Example:

    If myapp is running at cp3, and if cp2 now restarts, it does not restart │ │ │ │ myapp, as the order between the cp2 and cp3 nodes is undefined.

    Application myapp - Situation 4

    However, if cp1 also restarts, the function application:takeover/2 moves │ │ │ │ myapp to cp1, as cp1 has a higher priority than cp3 for this │ │ │ │ application. In this case, Module:start({takeover, cp3@cave}, StartArgs) is │ │ │ │ executed at cp1 to start the application.

    Application myapp - Situation 5

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/distributed.xhtml │ │ │ │ @@ -47,25 +47,25 @@ │ │ │ │ │ │ │ │

    A node is an executing Erlang runtime system that has been given a name, using │ │ │ │ the command-line flag -name (long names) or │ │ │ │ -sname (short names).

    The format of the node name is an atom name@host. name is the name given by │ │ │ │ the user. host is the full host name if long names are used, or the first part │ │ │ │ of the host name if short names are used. Function node() │ │ │ │ returns the name of the node.

    Example:

    % erl -name dilbert
    │ │ │ │ -(dilbert@uab.ericsson.se)1> node().
    │ │ │ │ +(dilbert@uab.ericsson.se)1> node().
    │ │ │ │  'dilbert@uab.ericsson.se'
    │ │ │ │  
    │ │ │ │  % erl -sname dilbert
    │ │ │ │ -(dilbert@uab)1> node().
    │ │ │ │ +(dilbert@uab)1> node().
    │ │ │ │  dilbert@uab

    The node name can also be given in runtime by calling net_kernel:start/1.

    Example:

    % erl
    │ │ │ │ -1> node().
    │ │ │ │ +1> node().
    │ │ │ │  nonode@nohost
    │ │ │ │ -2> net_kernel:start([dilbert,shortnames]).
    │ │ │ │ -{ok,<0.102.0>}
    │ │ │ │ -(dilbert@uab)3> node().
    │ │ │ │ +2> net_kernel:start([dilbert,shortnames]).
    │ │ │ │ +{ok,<0.102.0>}
    │ │ │ │ +(dilbert@uab)3> node().
    │ │ │ │  dilbert@uab

    Note

    A node with a long node name cannot communicate with a node with a short node │ │ │ │ name.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Node Connections │ │ │ │

    │ │ │ ├── OEBPS/design_principles.xhtml │ │ │ │ @@ -57,135 +57,135 @@ │ │ │ │ the code for a process in a generic part (a behaviour module) and a specific │ │ │ │ part (a callback module).

    The behaviour module is part of Erlang/OTP. To implement a process such as a │ │ │ │ supervisor, the user only needs to implement the callback module, which is to │ │ │ │ export a pre-defined set of functions, the callback functions.

    The following example illustrate how code can be divided into a generic and a │ │ │ │ specific part. Consider the following code (written in plain Erlang) for a │ │ │ │ simple server, which keeps track of a number of "channels". Other processes can │ │ │ │ allocate and free the channels by calling the functions alloc/0 and free/1, │ │ │ │ -respectively.

    -module(ch1).
    │ │ │ │ --export([start/0]).
    │ │ │ │ --export([alloc/0, free/1]).
    │ │ │ │ --export([init/0]).
    │ │ │ │ +respectively.

    -module(ch1).
    │ │ │ │ +-export([start/0]).
    │ │ │ │ +-export([alloc/0, free/1]).
    │ │ │ │ +-export([init/0]).
    │ │ │ │  
    │ │ │ │ -start() ->
    │ │ │ │ -    spawn(ch1, init, []).
    │ │ │ │ +start() ->
    │ │ │ │ +    spawn(ch1, init, []).
    │ │ │ │  
    │ │ │ │ -alloc() ->
    │ │ │ │ -    ch1 ! {self(), alloc},
    │ │ │ │ +alloc() ->
    │ │ │ │ +    ch1 ! {self(), alloc},
    │ │ │ │      receive
    │ │ │ │ -        {ch1, Res} ->
    │ │ │ │ +        {ch1, Res} ->
    │ │ │ │              Res
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -free(Ch) ->
    │ │ │ │ -    ch1 ! {free, Ch},
    │ │ │ │ +free(Ch) ->
    │ │ │ │ +    ch1 ! {free, Ch},
    │ │ │ │      ok.
    │ │ │ │  
    │ │ │ │ -init() ->
    │ │ │ │ -    register(ch1, self()),
    │ │ │ │ -    Chs = channels(),
    │ │ │ │ -    loop(Chs).
    │ │ │ │ +init() ->
    │ │ │ │ +    register(ch1, self()),
    │ │ │ │ +    Chs = channels(),
    │ │ │ │ +    loop(Chs).
    │ │ │ │  
    │ │ │ │ -loop(Chs) ->
    │ │ │ │ +loop(Chs) ->
    │ │ │ │      receive
    │ │ │ │ -        {From, alloc} ->
    │ │ │ │ -            {Ch, Chs2} = alloc(Chs),
    │ │ │ │ -            From ! {ch1, Ch},
    │ │ │ │ -            loop(Chs2);
    │ │ │ │ -        {free, Ch} ->
    │ │ │ │ -            Chs2 = free(Ch, Chs),
    │ │ │ │ -            loop(Chs2)
    │ │ │ │ -    end.

    The code for the server can be rewritten into a generic part server.erl:

    -module(server).
    │ │ │ │ --export([start/1]).
    │ │ │ │ --export([call/2, cast/2]).
    │ │ │ │ --export([init/1]).
    │ │ │ │ +        {From, alloc} ->
    │ │ │ │ +            {Ch, Chs2} = alloc(Chs),
    │ │ │ │ +            From ! {ch1, Ch},
    │ │ │ │ +            loop(Chs2);
    │ │ │ │ +        {free, Ch} ->
    │ │ │ │ +            Chs2 = free(Ch, Chs),
    │ │ │ │ +            loop(Chs2)
    │ │ │ │ +    end.

    The code for the server can be rewritten into a generic part server.erl:

    -module(server).
    │ │ │ │ +-export([start/1]).
    │ │ │ │ +-export([call/2, cast/2]).
    │ │ │ │ +-export([init/1]).
    │ │ │ │  
    │ │ │ │ -start(Mod) ->
    │ │ │ │ -    spawn(server, init, [Mod]).
    │ │ │ │ +start(Mod) ->
    │ │ │ │ +    spawn(server, init, [Mod]).
    │ │ │ │  
    │ │ │ │ -call(Name, Req) ->
    │ │ │ │ -    Name ! {call, self(), Req},
    │ │ │ │ +call(Name, Req) ->
    │ │ │ │ +    Name ! {call, self(), Req},
    │ │ │ │      receive
    │ │ │ │ -        {Name, Res} ->
    │ │ │ │ +        {Name, Res} ->
    │ │ │ │              Res
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -cast(Name, Req) ->
    │ │ │ │ -    Name ! {cast, Req},
    │ │ │ │ +cast(Name, Req) ->
    │ │ │ │ +    Name ! {cast, Req},
    │ │ │ │      ok.
    │ │ │ │  
    │ │ │ │ -init(Mod) ->
    │ │ │ │ -    register(Mod, self()),
    │ │ │ │ -    State = Mod:init(),
    │ │ │ │ -    loop(Mod, State).
    │ │ │ │ +init(Mod) ->
    │ │ │ │ +    register(Mod, self()),
    │ │ │ │ +    State = Mod:init(),
    │ │ │ │ +    loop(Mod, State).
    │ │ │ │  
    │ │ │ │ -loop(Mod, State) ->
    │ │ │ │ +loop(Mod, State) ->
    │ │ │ │      receive
    │ │ │ │ -        {call, From, Req} ->
    │ │ │ │ -            {Res, State2} = Mod:handle_call(Req, State),
    │ │ │ │ -            From ! {Mod, Res},
    │ │ │ │ -            loop(Mod, State2);
    │ │ │ │ -        {cast, Req} ->
    │ │ │ │ -            State2 = Mod:handle_cast(Req, State),
    │ │ │ │ -            loop(Mod, State2)
    │ │ │ │ -    end.

    And a callback module ch2.erl:

    -module(ch2).
    │ │ │ │ --export([start/0]).
    │ │ │ │ --export([alloc/0, free/1]).
    │ │ │ │ --export([init/0, handle_call/2, handle_cast/2]).
    │ │ │ │ -
    │ │ │ │ -start() ->
    │ │ │ │ -    server:start(ch2).
    │ │ │ │ -
    │ │ │ │ -alloc() ->
    │ │ │ │ -    server:call(ch2, alloc).
    │ │ │ │ -
    │ │ │ │ -free(Ch) ->
    │ │ │ │ -    server:cast(ch2, {free, Ch}).
    │ │ │ │ +        {call, From, Req} ->
    │ │ │ │ +            {Res, State2} = Mod:handle_call(Req, State),
    │ │ │ │ +            From ! {Mod, Res},
    │ │ │ │ +            loop(Mod, State2);
    │ │ │ │ +        {cast, Req} ->
    │ │ │ │ +            State2 = Mod:handle_cast(Req, State),
    │ │ │ │ +            loop(Mod, State2)
    │ │ │ │ +    end.

    And a callback module ch2.erl:

    -module(ch2).
    │ │ │ │ +-export([start/0]).
    │ │ │ │ +-export([alloc/0, free/1]).
    │ │ │ │ +-export([init/0, handle_call/2, handle_cast/2]).
    │ │ │ │ +
    │ │ │ │ +start() ->
    │ │ │ │ +    server:start(ch2).
    │ │ │ │ +
    │ │ │ │ +alloc() ->
    │ │ │ │ +    server:call(ch2, alloc).
    │ │ │ │ +
    │ │ │ │ +free(Ch) ->
    │ │ │ │ +    server:cast(ch2, {free, Ch}).
    │ │ │ │  
    │ │ │ │ -init() ->
    │ │ │ │ -    channels().
    │ │ │ │ +init() ->
    │ │ │ │ +    channels().
    │ │ │ │  
    │ │ │ │ -handle_call(alloc, Chs) ->
    │ │ │ │ -    alloc(Chs). % => {Ch,Chs2}
    │ │ │ │ +handle_call(alloc, Chs) ->
    │ │ │ │ +    alloc(Chs). % => {Ch,Chs2}
    │ │ │ │  
    │ │ │ │ -handle_cast({free, Ch}, Chs) ->
    │ │ │ │ -    free(Ch, Chs). % => Chs2

    Notice the following:

    • The code in server can be reused to build many different servers.
    • The server name, in this example the atom ch2, is hidden from the users of │ │ │ │ +handle_cast({free, Ch}, Chs) -> │ │ │ │ + free(Ch, Chs). % => Chs2

    Notice the following:

    • The code in server can be reused to build many different servers.
    • The server name, in this example the atom ch2, is hidden from the users of │ │ │ │ the client functions. This means that the name can be changed without │ │ │ │ affecting them.
    • The protocol (messages sent to and received from the server) is also hidden. │ │ │ │ This is good programming practice and allows one to change the protocol │ │ │ │ without changing the code using the interface functions.
    • The functionality of server can be extended without having to change ch2 │ │ │ │ or any other callback module.

    In ch1.erl and ch2.erl above, the implementation of channels/0, alloc/1, │ │ │ │ and free/2 has been intentionally left out, as it is not relevant to the │ │ │ │ example. For completeness, one way to write these functions is given below. This │ │ │ │ is an example only, a realistic implementation must be able to handle situations │ │ │ │ -like running out of channels to allocate, and so on.

    channels() ->
    │ │ │ │ -   {_Allocated = [], _Free = lists:seq(1, 100)}.
    │ │ │ │ +like running out of channels to allocate, and so on.

    channels() ->
    │ │ │ │ +   {_Allocated = [], _Free = lists:seq(1, 100)}.
    │ │ │ │  
    │ │ │ │ -alloc({Allocated, [H|T] = _Free}) ->
    │ │ │ │ -   {H, {[H|Allocated], T}}.
    │ │ │ │ +alloc({Allocated, [H|T] = _Free}) ->
    │ │ │ │ +   {H, {[H|Allocated], T}}.
    │ │ │ │  
    │ │ │ │ -free(Ch, {Alloc, Free} = Channels) ->
    │ │ │ │ -   case lists:member(Ch, Alloc) of
    │ │ │ │ +free(Ch, {Alloc, Free} = Channels) ->
    │ │ │ │ +   case lists:member(Ch, Alloc) of
    │ │ │ │        true ->
    │ │ │ │ -         {lists:delete(Ch, Alloc), [Ch|Free]};
    │ │ │ │ +         {lists:delete(Ch, Alloc), [Ch|Free]};
    │ │ │ │        false ->
    │ │ │ │           Channels
    │ │ │ │     end.

    Code written without using behaviours can be more efficient, but the increased │ │ │ │ efficiency is at the expense of generality. The ability to manage all │ │ │ │ applications in the system in a consistent manner is important.

    Using behaviours also makes it easier to read and understand code written by │ │ │ │ other programmers. Improvised programming structures, while possibly more │ │ │ │ efficient, are always more difficult to understand.

    The server module corresponds, greatly simplified, to the Erlang/OTP behaviour │ │ │ │ gen_server.

    The standard Erlang/OTP behaviours are:

    • gen_server

      For implementing the server of a client-server relation

    • gen_statem

      For implementing state machines

    • gen_event

      For implementing event handling functionality

    • supervisor

      For implementing a supervisor in a supervision tree

    The compiler understands the module attribute -behaviour(Behaviour) and issues │ │ │ │ -warnings about missing callback functions, for example:

    -module(chs3).
    │ │ │ │ --behaviour(gen_server).
    │ │ │ │ +warnings about missing callback functions, for example:

    -module(chs3).
    │ │ │ │ +-behaviour(gen_server).
    │ │ │ │  ...
    │ │ │ │  
    │ │ │ │ -3> c(chs3).
    │ │ │ │ +3> c(chs3).
    │ │ │ │  ./chs3.erl:10: Warning: undefined call-back function handle_call/3
    │ │ │ │ -{ok,chs3}

    │ │ │ │ +{ok,chs3}

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Applications │ │ │ │

    │ │ │ │

    Erlang/OTP comes with a number of components, each implementing some specific │ │ │ │ functionality. Components are with Erlang/OTP terminology called applications. │ │ │ ├── OEBPS/data_types.xhtml │ │ │ │ @@ -97,18 +97,18 @@ │ │ │ │ │ │ │ │ Representation of Floating Point Numbers │ │ │ │ │ │ │ │

    When working with floats you may not see what you expect when printing or doing │ │ │ │ arithmetic operations. This is because floats are represented by a fixed number │ │ │ │ of bits in a base-2 system while printed floats are represented with a base-10 │ │ │ │ system. Erlang uses 64-bit floats. Here are examples of this phenomenon:

    1> 0.1+0.2.
    │ │ │ │ -0.30000000000000004

    The real numbers 0.1 and 0.2 cannot be represented exactly as floats.

    1> {36028797018963968.0, 36028797018963968 == 36028797018963968.0,
    │ │ │ │ -  36028797018963970.0, 36028797018963970 == 36028797018963970.0}.
    │ │ │ │ -{3.602879701896397e16, true,
    │ │ │ │ - 3.602879701896397e16, false}.

    The value 36028797018963968 can be represented exactly as a float value but │ │ │ │ +0.30000000000000004

    The real numbers 0.1 and 0.2 cannot be represented exactly as floats.

    1> {36028797018963968.0, 36028797018963968 == 36028797018963968.0,
    │ │ │ │ +  36028797018963970.0, 36028797018963970 == 36028797018963970.0}.
    │ │ │ │ +{3.602879701896397e16, true,
    │ │ │ │ + 3.602879701896397e16, false}.

    The value 36028797018963968 can be represented exactly as a float value but │ │ │ │ Erlang's pretty printer rounds 36028797018963968.0 to 3.602879701896397e16 │ │ │ │ (=36028797018963970.0) as all values in the range │ │ │ │ [36028797018963966.0, 36028797018963972.0] are represented by │ │ │ │ 36028797018963968.0.

    For more information about floats and issues with them see:

    If you need to work with exact decimal fractions, for instance to represent │ │ │ │ money, it is recommended to use a library that handles that, or work in │ │ │ │ cents instead of dollars or euros so that decimal fractions are not needed.

    Also note that Erlang's floats do not exactly match IEEE 754 floats, │ │ │ │ in that neither Inf nor NaN are supported in Erlang. Any │ │ │ │ @@ -142,52 +142,52 @@ │ │ │ │ by eight are called binaries.

    Examples:

    1> <<10,20>>.
    │ │ │ │  <<10,20>>
    │ │ │ │  2> <<"ABC">>.
    │ │ │ │  <<"ABC">>
    │ │ │ │  3> <<1:1,0:1>>.
    │ │ │ │  <<2:2>>

    The is_bitstring/1 BIF tests whether a │ │ │ │ term is a bit string, and the is_binary/1 │ │ │ │ -BIF tests whether a term is a binary.

    Examples:

    1> is_bitstring(<<1:1>>).
    │ │ │ │ +BIF tests whether a term is a binary.

    Examples:

    1> is_bitstring(<<1:1>>).
    │ │ │ │  true
    │ │ │ │ -2> is_binary(<<1:1>>).
    │ │ │ │ +2> is_binary(<<1:1>>).
    │ │ │ │  false
    │ │ │ │ -3> is_binary(<<42>>).
    │ │ │ │ +3> is_binary(<<42>>).
    │ │ │ │  true
    │ │ │ │  

    For more examples, see Programming Examples.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Reference │ │ │ │

    │ │ │ │

    A term that is unique │ │ │ │ among connected nodes. A reference is created by calling the │ │ │ │ make_ref/0 BIF. The │ │ │ │ is_reference/1 BIF tests whether a term │ │ │ │ -is a reference.

    Examples:

    1> Ref = make_ref().
    │ │ │ │ +is a reference.

    Examples:

    1> Ref = make_ref().
    │ │ │ │  #Ref<0.76482849.3801088007.198204>
    │ │ │ │ -2> is_reference(Ref).
    │ │ │ │ +2> is_reference(Ref).
    │ │ │ │  true

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Fun │ │ │ │

    │ │ │ │

    A fun is a functional object. Funs make it possible to create an anonymous │ │ │ │ function and pass the function itself — not its name — as argument to other │ │ │ │ -functions.

    Examples:

    1> Fun1 = fun (X) -> X+1 end.
    │ │ │ │ +functions.

    Examples:

    1> Fun1 = fun (X) -> X+1 end.
    │ │ │ │  #Fun<erl_eval.6.39074546>
    │ │ │ │ -2> Fun1(2).
    │ │ │ │ +2> Fun1(2).
    │ │ │ │  3

    The is_function/1 and is_function/2 │ │ │ │ -BIFs tests whether a term is a fun.

    Examples:

    1> F = fun() -> ok end.
    │ │ │ │ +BIFs tests whether a term is a fun.

    Examples:

    1> F = fun() -> ok end.
    │ │ │ │  #Fun<erl_eval.43.105768164>
    │ │ │ │ -2> is_function(F).
    │ │ │ │ +2> is_function(F).
    │ │ │ │  true
    │ │ │ │ -3> is_function(F, 0).
    │ │ │ │ +3> is_function(F, 0).
    │ │ │ │  true
    │ │ │ │ -4> is_function(F, 1).
    │ │ │ │ +4> is_function(F, 1).
    │ │ │ │  false

    Read more about funs in Fun Expressions. For more │ │ │ │ examples, see Programming Examples.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Port Identifier │ │ │ │

    │ │ │ │ @@ -205,94 +205,94 @@ │ │ │ │ for a new process after a while.

    The BIF self/0 returns the Pid of the calling process. When │ │ │ │ creating a new process, the parent │ │ │ │ process will be able to get the Pid of the child process either via the return │ │ │ │ value, as is the case when calling the spawn/3 BIF, or via │ │ │ │ a message, which is the case when calling the │ │ │ │ spawn_request/5 BIF. A Pid is typically used when │ │ │ │ when sending a process a signal. The │ │ │ │ -is_pid/1 BIF tests whether a term is a Pid.

    Example:

    -module(m).
    │ │ │ │ --export([loop/0]).
    │ │ │ │ +is_pid/1 BIF tests whether a term is a Pid.

    Example:

    -module(m).
    │ │ │ │ +-export([loop/0]).
    │ │ │ │  
    │ │ │ │ -loop() ->
    │ │ │ │ +loop() ->
    │ │ │ │      receive
    │ │ │ │          who_are_you ->
    │ │ │ │ -            io:format("I am ~p~n", [self()]),
    │ │ │ │ -            loop()
    │ │ │ │ +            io:format("I am ~p~n", [self()]),
    │ │ │ │ +            loop()
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -1> P = spawn(m, loop, []).
    │ │ │ │ +1> P = spawn(m, loop, []).
    │ │ │ │  <0.58.0>
    │ │ │ │  2> P ! who_are_you.
    │ │ │ │  I am <0.58.0>
    │ │ │ │  who_are_you

    Read more about processes in Processes.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Tuple │ │ │ │

    │ │ │ │

    A tuple is a compound data type with a fixed number of terms:

    {Term1,...,TermN}

    Each term Term in the tuple is called an element. The number of elements is │ │ │ │ -said to be the size of the tuple.

    There exists a number of BIFs to manipulate tuples.

    Examples:

    1> P = {adam,24,{july,29}}.
    │ │ │ │ -{adam,24,{july,29}}
    │ │ │ │ -2> element(1,P).
    │ │ │ │ +said to be the size of the tuple.

    There exists a number of BIFs to manipulate tuples.

    Examples:

    1> P = {adam,24,{july,29}}.
    │ │ │ │ +{adam,24,{july,29}}
    │ │ │ │ +2> element(1,P).
    │ │ │ │  adam
    │ │ │ │ -3> element(3,P).
    │ │ │ │ -{july,29}
    │ │ │ │ -4> P2 = setelement(2,P,25).
    │ │ │ │ -{adam,25,{july,29}}
    │ │ │ │ -5> tuple_size(P).
    │ │ │ │ +3> element(3,P).
    │ │ │ │ +{july,29}
    │ │ │ │ +4> P2 = setelement(2,P,25).
    │ │ │ │ +{adam,25,{july,29}}
    │ │ │ │ +5> tuple_size(P).
    │ │ │ │  3
    │ │ │ │ -6> tuple_size({}).
    │ │ │ │ +6> tuple_size({}).
    │ │ │ │  0
    │ │ │ │ -7> is_tuple({a,b,c}).
    │ │ │ │ +7> is_tuple({a,b,c}).
    │ │ │ │  true

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Map │ │ │ │

    │ │ │ │

    A map is a compound data type with a variable number of key-value associations:

    #{Key1 => Value1, ..., KeyN => ValueN}

    Each key-value association in the map is called an association pair. The key │ │ │ │ and value parts of the pair are called elements. The number of association │ │ │ │ -pairs is said to be the size of the map.

    There exists a number of BIFs to manipulate maps.

    Examples:

    1> M1 = #{name => adam, age => 24, date => {july,29}}.
    │ │ │ │ -#{age => 24,date => {july,29},name => adam}
    │ │ │ │ -2> maps:get(name, M1).
    │ │ │ │ +pairs is said to be the size of the map.

    There exists a number of BIFs to manipulate maps.

    Examples:

    1> M1 = #{name => adam, age => 24, date => {july,29}}.
    │ │ │ │ +#{age => 24,date => {july,29},name => adam}
    │ │ │ │ +2> maps:get(name, M1).
    │ │ │ │  adam
    │ │ │ │ -3> maps:get(date, M1).
    │ │ │ │ -{july,29}
    │ │ │ │ -4> M2 = maps:update(age, 25, M1).
    │ │ │ │ -#{age => 25,date => {july,29},name => adam}
    │ │ │ │ -5> map_size(M).
    │ │ │ │ +3> maps:get(date, M1).
    │ │ │ │ +{july,29}
    │ │ │ │ +4> M2 = maps:update(age, 25, M1).
    │ │ │ │ +#{age => 25,date => {july,29},name => adam}
    │ │ │ │ +5> map_size(M).
    │ │ │ │  3
    │ │ │ │ -6> map_size(#{}).
    │ │ │ │ +6> map_size(#{}).
    │ │ │ │  0

    A collection of maps processing functions are found in module maps │ │ │ │ in STDLIB.

    Read more about maps in Map Expressions.

    Change

    Maps were introduced as an experimental feature in Erlang/OTP R17. Their │ │ │ │ functionality was extended and became fully supported in Erlang/OTP 18.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ List │ │ │ │

    │ │ │ │

    A list is a compound data type with a variable number of terms.

    [Term1,...,TermN]

    Each term Term in the list is called an element. The number of elements is │ │ │ │ said to be the length of the list.

    Formally, a list is either the empty list [] or consists of a head (first │ │ │ │ element) and a tail (remainder of the list). The tail is also a list. The │ │ │ │ latter can be expressed as [H|T]. The notation [Term1,...,TermN] above is │ │ │ │ equivalent with the list [Term1|[...|[TermN|[]]]].

    Example:

    [] is a list, thus
    [c|[]] is a list, thus
    [b|[c|[]]] is a list, thus
    [a|[b|[c|[]]]] is a list, or in short [a,b,c]

    A list where the tail is a list is sometimes called a proper list. It is │ │ │ │ allowed to have a list where the tail is not a list, for example, [a|b]. │ │ │ │ -However, this type of list is of little practical use.

    Examples:

    1> L1 = [a,2,{c,4}].
    │ │ │ │ -[a,2,{c,4}]
    │ │ │ │ -2> [H|T] = L1.
    │ │ │ │ -[a,2,{c,4}]
    │ │ │ │ +However, this type of list is of little practical use.

    Examples:

    1> L1 = [a,2,{c,4}].
    │ │ │ │ +[a,2,{c,4}]
    │ │ │ │ +2> [H|T] = L1.
    │ │ │ │ +[a,2,{c,4}]
    │ │ │ │  3> H.
    │ │ │ │  a
    │ │ │ │  4> T.
    │ │ │ │ -[2,{c,4}]
    │ │ │ │ -5> L2 = [d|T].
    │ │ │ │ -[d,2,{c,4}]
    │ │ │ │ -6> length(L1).
    │ │ │ │ +[2,{c,4}]
    │ │ │ │ +5> L2 = [d|T].
    │ │ │ │ +[d,2,{c,4}]
    │ │ │ │ +6> length(L1).
    │ │ │ │  3
    │ │ │ │ -7> length([]).
    │ │ │ │ +7> length([]).
    │ │ │ │  0

    A collection of list processing functions are found in module │ │ │ │ lists in STDLIB.

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

    │ │ │ │ @@ -412,41 +412,41 @@ │ │ │ │ Record │ │ │ │ │ │ │ │

    A record is a data structure for storing a fixed number of elements. It has │ │ │ │ named fields and is similar to a struct in C. However, a record is not a true │ │ │ │ data type. Instead, record expressions are translated to tuple expressions │ │ │ │ during compilation. Therefore, record expressions are not understood by the │ │ │ │ shell unless special actions are taken. For details, see module shell │ │ │ │ -in STDLIB.

    Examples:

    -module(person).
    │ │ │ │ --export([new/2]).
    │ │ │ │ +in STDLIB.

    Examples:

    -module(person).
    │ │ │ │ +-export([new/2]).
    │ │ │ │  
    │ │ │ │ --record(person, {name, age}).
    │ │ │ │ +-record(person, {name, age}).
    │ │ │ │  
    │ │ │ │ -new(Name, Age) ->
    │ │ │ │ -    #person{name=Name, age=Age}.
    │ │ │ │ +new(Name, Age) ->
    │ │ │ │ +    #person{name=Name, age=Age}.
    │ │ │ │  
    │ │ │ │ -1> person:new(ernie, 44).
    │ │ │ │ -{person,ernie,44}

    Read more about records in Records. More examples are │ │ │ │ +1> person:new(ernie, 44). │ │ │ │ +{person,ernie,44}

    Read more about records in Records. More examples are │ │ │ │ found in Programming Examples.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Boolean │ │ │ │

    │ │ │ │

    There is no Boolean data type in Erlang. Instead the atoms true and false │ │ │ │ are used to denote Boolean values. The is_boolean/1 │ │ │ │ BIF tests whether a term is a boolean.

    Examples:

    1> 2 =< 3.
    │ │ │ │  true
    │ │ │ │  2> true or false.
    │ │ │ │  true
    │ │ │ │ -3> is_boolean(true).
    │ │ │ │ +3> is_boolean(true).
    │ │ │ │  true
    │ │ │ │ -4> is_boolean(false).
    │ │ │ │ +4> is_boolean(false).
    │ │ │ │  true
    │ │ │ │ -5> is_boolean(ok).
    │ │ │ │ +5> is_boolean(ok).
    │ │ │ │  false

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Escape Sequences │ │ │ │

    │ │ │ │

    Within strings ("-delimited), quoted atoms, and the content of │ │ │ │ @@ -464,44 +464,44 @@ │ │ │ │ ~b or ~s sigils the escape sequences for normal │ │ │ │ strings, above, are used.

    Change

    Triple-quoted strings and sigils were introduced in Erlang/OTP 27.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Type Conversions │ │ │ │

    │ │ │ │ -

    There are a number of BIFs for type conversions.

    Examples:

    1> atom_to_list(hello).
    │ │ │ │ +

    There are a number of BIFs for type conversions.

    Examples:

    1> atom_to_list(hello).
    │ │ │ │  "hello"
    │ │ │ │ -2> list_to_atom("hello").
    │ │ │ │ +2> list_to_atom("hello").
    │ │ │ │  hello
    │ │ │ │ -3> binary_to_list(<<"hello">>).
    │ │ │ │ +3> binary_to_list(<<"hello">>).
    │ │ │ │  "hello"
    │ │ │ │ -4> binary_to_list(<<104,101,108,108,111>>).
    │ │ │ │ +4> binary_to_list(<<104,101,108,108,111>>).
    │ │ │ │  "hello"
    │ │ │ │ -5> list_to_binary("hello").
    │ │ │ │ -<<104,101,108,108,111>>
    │ │ │ │ -6> float_to_list(7.0).
    │ │ │ │ +5> list_to_binary("hello").
    │ │ │ │ +<<104,101,108,108,111>>
    │ │ │ │ +6> float_to_list(7.0).
    │ │ │ │  "7.00000000000000000000e+00"
    │ │ │ │ -7> list_to_float("7.000e+00").
    │ │ │ │ +7> list_to_float("7.000e+00").
    │ │ │ │  7.0
    │ │ │ │ -8> integer_to_list(77).
    │ │ │ │ +8> integer_to_list(77).
    │ │ │ │  "77"
    │ │ │ │ -9> list_to_integer("77").
    │ │ │ │ +9> list_to_integer("77").
    │ │ │ │  77
    │ │ │ │ -10> tuple_to_list({a,b,c}).
    │ │ │ │ -[a,b,c]
    │ │ │ │ -11> list_to_tuple([a,b,c]).
    │ │ │ │ -{a,b,c}
    │ │ │ │ -12> term_to_binary({a,b,c}).
    │ │ │ │ -<<131,104,3,100,0,1,97,100,0,1,98,100,0,1,99>>
    │ │ │ │ -13> binary_to_term(<<131,104,3,100,0,1,97,100,0,1,98,100,0,1,99>>).
    │ │ │ │ -{a,b,c}
    │ │ │ │ -14> binary_to_integer(<<"77">>).
    │ │ │ │ +10> tuple_to_list({a,b,c}).
    │ │ │ │ +[a,b,c]
    │ │ │ │ +11> list_to_tuple([a,b,c]).
    │ │ │ │ +{a,b,c}
    │ │ │ │ +12> term_to_binary({a,b,c}).
    │ │ │ │ +<<131,104,3,100,0,1,97,100,0,1,98,100,0,1,99>>
    │ │ │ │ +13> binary_to_term(<<131,104,3,100,0,1,97,100,0,1,98,100,0,1,99>>).
    │ │ │ │ +{a,b,c}
    │ │ │ │ +14> binary_to_integer(<<"77">>).
    │ │ │ │  77
    │ │ │ │ -15> integer_to_binary(77).
    │ │ │ │ -<<"77">>
    │ │ │ │ -16> float_to_binary(7.0).
    │ │ │ │ -<<"7.00000000000000000000e+00">>
    │ │ │ │ -17> binary_to_float(<<"7.000e+00">>).
    │ │ │ │ +15> integer_to_binary(77).
    │ │ │ │ +<<"77">>
    │ │ │ │ +16> float_to_binary(7.0).
    │ │ │ │ +<<"7.00000000000000000000e+00">>
    │ │ │ │ +17> binary_to_float(<<"7.000e+00">>).
    │ │ │ │  7.0
    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/create_target.xhtml │ │ │ │ @@ -43,21 +43,21 @@ │ │ │ │ Creating a Target System │ │ │ │ │ │ │ │

    It is assumed that you have a working Erlang/OTP system structured according to │ │ │ │ the OTP design principles.

    Step 1. Create a .rel file (see the rel(4) manual page in │ │ │ │ SASL), which specifies the ERTS version and lists all applications that are to │ │ │ │ be included in the new basic target system. An example is the following │ │ │ │ mysystem.rel file:

    %% mysystem.rel
    │ │ │ │ -{release,
    │ │ │ │ - {"MYSYSTEM", "FIRST"},
    │ │ │ │ - {erts, "5.10.4"},
    │ │ │ │ - [{kernel, "2.16.4"},
    │ │ │ │ -  {stdlib, "1.19.4"},
    │ │ │ │ -  {sasl, "2.3.4"},
    │ │ │ │ -  {pea, "1.0"}]}.

    The listed applications are not only original Erlang/OTP applications but │ │ │ │ +{release, │ │ │ │ + {"MYSYSTEM", "FIRST"}, │ │ │ │ + {erts, "5.10.4"}, │ │ │ │ + [{kernel, "2.16.4"}, │ │ │ │ + {stdlib, "1.19.4"}, │ │ │ │ + {sasl, "2.3.4"}, │ │ │ │ + {pea, "1.0"}]}.

    The listed applications are not only original Erlang/OTP applications but │ │ │ │ possibly also new applications that you have written (here exemplified by the │ │ │ │ application Pea (pea)).

    Step 2. Start Erlang/OTP from the directory where the mysystem.rel file │ │ │ │ resides:

    % erl -pa /home/user/target_system/myapps/pea-1.0/ebin

    The -pa argument prepends the path to the ebin directory for │ │ │ │ the Pea application to the code path.

    Step 3. Create the target system:

    1> target_system:create("mysystem").

    The function target_system:create/1 performs the following:

    1. Reads the file mysystem.rel and creates a new file plain.rel. │ │ │ │ The new file is identical to the original, except that it only │ │ │ │ lists the Kernel and STDLIB applications.

    2. From the files mysystem.rel and plain.rel creates the files │ │ │ │ mysystem.script, mysystem.boot, plain.script, and plain.boot │ │ │ │ @@ -147,25 +147,25 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Creating the Next Version │ │ │ │ │ │ │ │

      In this example the Pea application has been changed, and so are the │ │ │ │ applications ERTS, Kernel, STDLIB and SASL.

      Step 1. Create the file .rel:

      %% mysystem2.rel
      │ │ │ │ -{release,
      │ │ │ │ - {"MYSYSTEM", "SECOND"},
      │ │ │ │ - {erts, "6.0"},
      │ │ │ │ - [{kernel, "3.0"},
      │ │ │ │ -  {stdlib, "2.0"},
      │ │ │ │ -  {sasl, "2.4"},
      │ │ │ │ -  {pea, "2.0"}]}.

      Step 2. Create the application upgrade file (see │ │ │ │ +{release, │ │ │ │ + {"MYSYSTEM", "SECOND"}, │ │ │ │ + {erts, "6.0"}, │ │ │ │ + [{kernel, "3.0"}, │ │ │ │ + {stdlib, "2.0"}, │ │ │ │ + {sasl, "2.4"}, │ │ │ │ + {pea, "2.0"}]}.

    Step 2. Create the application upgrade file (see │ │ │ │ appup in SASL) for Pea, for example:

    %% pea.appup
    │ │ │ │ -{"2.0",
    │ │ │ │ - [{"1.0",[{load_module,pea_lib}]}],
    │ │ │ │ - [{"1.0",[{load_module,pea_lib}]}]}.

    Step 3. From the directory where the file mysystem2.rel resides, start the │ │ │ │ +{"2.0", │ │ │ │ + [{"1.0",[{load_module,pea_lib}]}], │ │ │ │ + [{"1.0",[{load_module,pea_lib}]}]}.

    Step 3. From the directory where the file mysystem2.rel resides, start the │ │ │ │ Erlang/OTP system, giving the path to the new version of Pea:

    % erl -pa /home/user/target_system/myapps/pea-2.0/ebin

    Step 4. Create the release upgrade file (see relup │ │ │ │ in SASL):

    1> systools:make_relup("mysystem2",["mysystem"],["mysystem"],
    │ │ │ │      [{path,["/home/user/target_system/myapps/pea-1.0/ebin",
    │ │ │ │      "/my/old/erlang/lib/*/ebin"]}]).

    Here "mysystem" is the base release and "mysystem2" is the release to │ │ │ │ upgrade to.

    The path option is used for pointing out the old version of all applications. │ │ │ │ (The new versions are already in the code path - assuming of course that the │ │ │ │ Erlang node on which this is executed is running the correct version of │ │ │ │ @@ -197,287 +197,287 @@ │ │ │ │ {continue_after_restart,"FIRST",[]} │ │ │ │ heart: Tue Apr 1 12:15:10 2014: Erlang has closed. │ │ │ │ heart: Tue Apr 1 12:15:11 2014: Executed "/usr/local/erl-target/bin/start /usr/local/erl-target/releases/new_start_erl.data" -> 0. Terminating. │ │ │ │ [End]

    The above return value and output after the call to │ │ │ │ release_handler:install_release/1 means that the release_handler has │ │ │ │ restarted the node by using heart. This is always done when the upgrade │ │ │ │ involves a change of the applications ERTS, Kernel, STDLIB, or SASL. For more │ │ │ │ -information, see Upgrade when Erlang/OTP has Changed.

    The node is accessible through a new pipe:

    % /usr/local/erl-target/bin/to_erl /tmp/erlang.pipe.2

    List the available releases in the system:

    1> release_handler:which_releases().
    │ │ │ │ -[{"MYSYSTEM","SECOND",
    │ │ │ │ -  ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"],
    │ │ │ │ -  current},
    │ │ │ │ - {"MYSYSTEM","FIRST",
    │ │ │ │ -  ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"],
    │ │ │ │ -  permanent}]

    Our new release, "SECOND", is now the current release, but we can also see that │ │ │ │ +information, see Upgrade when Erlang/OTP has Changed.

    The node is accessible through a new pipe:

    % /usr/local/erl-target/bin/to_erl /tmp/erlang.pipe.2

    List the available releases in the system:

    1> release_handler:which_releases().
    │ │ │ │ +[{"MYSYSTEM","SECOND",
    │ │ │ │ +  ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"],
    │ │ │ │ +  current},
    │ │ │ │ + {"MYSYSTEM","FIRST",
    │ │ │ │ +  ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"],
    │ │ │ │ +  permanent}]

    Our new release, "SECOND", is now the current release, but we can also see that │ │ │ │ our "FIRST" release is still permanent. This means that if the node would be │ │ │ │ -restarted now, it would come up running the "FIRST" release again.

    Step 3. Make the new release permanent:

    2> release_handler:make_permanent("SECOND").

    Check the releases again:

    3> release_handler:which_releases().
    │ │ │ │ -[{"MYSYSTEM","SECOND",
    │ │ │ │ -  ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"],
    │ │ │ │ -  permanent},
    │ │ │ │ - {"MYSYSTEM","FIRST",
    │ │ │ │ -  ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"],
    │ │ │ │ -  old}]

    We see that the new release version is permanent, so it would be safe to │ │ │ │ +restarted now, it would come up running the "FIRST" release again.

    Step 3. Make the new release permanent:

    2> release_handler:make_permanent("SECOND").

    Check the releases again:

    3> release_handler:which_releases().
    │ │ │ │ +[{"MYSYSTEM","SECOND",
    │ │ │ │ +  ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"],
    │ │ │ │ +  permanent},
    │ │ │ │ + {"MYSYSTEM","FIRST",
    │ │ │ │ +  ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"],
    │ │ │ │ +  old}]

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

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Listing of target_system.erl │ │ │ │

    │ │ │ │

    This module can also be found in the examples directory of the SASL │ │ │ │ application.

    
    │ │ │ │ --module(target_system).
    │ │ │ │ --export([create/1, create/2, install/2]).
    │ │ │ │ +-module(target_system).
    │ │ │ │ +-export([create/1, create/2, install/2]).
    │ │ │ │  
    │ │ │ │  %% Note: RelFileName below is the *stem* without trailing .rel,
    │ │ │ │  %% .script etc.
    │ │ │ │  %%
    │ │ │ │  
    │ │ │ │  %% create(RelFileName)
    │ │ │ │  %%
    │ │ │ │ -create(RelFileName) ->
    │ │ │ │ -    create(RelFileName,[]).
    │ │ │ │ +create(RelFileName) ->
    │ │ │ │ +    create(RelFileName,[]).
    │ │ │ │  
    │ │ │ │ -create(RelFileName,SystoolsOpts) ->
    │ │ │ │ +create(RelFileName,SystoolsOpts) ->
    │ │ │ │      RelFile = RelFileName ++ ".rel",
    │ │ │ │ -    Dir = filename:dirname(RelFileName),
    │ │ │ │ -    PlainRelFileName = filename:join(Dir,"plain"),
    │ │ │ │ +    Dir = filename:dirname(RelFileName),
    │ │ │ │ +    PlainRelFileName = filename:join(Dir,"plain"),
    │ │ │ │      PlainRelFile = PlainRelFileName ++ ".rel",
    │ │ │ │ -    io:fwrite("Reading file: ~ts ...~n", [RelFile]),
    │ │ │ │ -    {ok, [RelSpec]} = file:consult(RelFile),
    │ │ │ │ -    io:fwrite("Creating file: ~ts from ~ts ...~n",
    │ │ │ │ -              [PlainRelFile, RelFile]),
    │ │ │ │ -    {release,
    │ │ │ │ -     {RelName, RelVsn},
    │ │ │ │ -     {erts, ErtsVsn},
    │ │ │ │ -     AppVsns} = RelSpec,
    │ │ │ │ -    PlainRelSpec = {release,
    │ │ │ │ -                    {RelName, RelVsn},
    │ │ │ │ -                    {erts, ErtsVsn},
    │ │ │ │ -                    lists:filter(fun({kernel, _}) ->
    │ │ │ │ +    io:fwrite("Reading file: ~ts ...~n", [RelFile]),
    │ │ │ │ +    {ok, [RelSpec]} = file:consult(RelFile),
    │ │ │ │ +    io:fwrite("Creating file: ~ts from ~ts ...~n",
    │ │ │ │ +              [PlainRelFile, RelFile]),
    │ │ │ │ +    {release,
    │ │ │ │ +     {RelName, RelVsn},
    │ │ │ │ +     {erts, ErtsVsn},
    │ │ │ │ +     AppVsns} = RelSpec,
    │ │ │ │ +    PlainRelSpec = {release,
    │ │ │ │ +                    {RelName, RelVsn},
    │ │ │ │ +                    {erts, ErtsVsn},
    │ │ │ │ +                    lists:filter(fun({kernel, _}) ->
    │ │ │ │                                           true;
    │ │ │ │ -                                    ({stdlib, _}) ->
    │ │ │ │ +                                    ({stdlib, _}) ->
    │ │ │ │                                           true;
    │ │ │ │ -                                    (_) ->
    │ │ │ │ +                                    (_) ->
    │ │ │ │                                           false
    │ │ │ │ -                                 end, AppVsns)
    │ │ │ │ -                   },
    │ │ │ │ -    {ok, Fd} = file:open(PlainRelFile, [write]),
    │ │ │ │ -    io:fwrite(Fd, "~p.~n", [PlainRelSpec]),
    │ │ │ │ -    file:close(Fd),
    │ │ │ │ -
    │ │ │ │ -    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
    │ │ │ │ -	      [PlainRelFileName,PlainRelFileName]),
    │ │ │ │ -    make_script(PlainRelFileName,SystoolsOpts),
    │ │ │ │ -
    │ │ │ │ -    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
    │ │ │ │ -              [RelFileName, RelFileName]),
    │ │ │ │ -    make_script(RelFileName,SystoolsOpts),
    │ │ │ │ +                                 end, AppVsns)
    │ │ │ │ +                   },
    │ │ │ │ +    {ok, Fd} = file:open(PlainRelFile, [write]),
    │ │ │ │ +    io:fwrite(Fd, "~p.~n", [PlainRelSpec]),
    │ │ │ │ +    file:close(Fd),
    │ │ │ │ +
    │ │ │ │ +    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
    │ │ │ │ +	      [PlainRelFileName,PlainRelFileName]),
    │ │ │ │ +    make_script(PlainRelFileName,SystoolsOpts),
    │ │ │ │ +
    │ │ │ │ +    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
    │ │ │ │ +              [RelFileName, RelFileName]),
    │ │ │ │ +    make_script(RelFileName,SystoolsOpts),
    │ │ │ │  
    │ │ │ │      TarFileName = RelFileName ++ ".tar.gz",
    │ │ │ │ -    io:fwrite("Creating tar file ~ts ...~n", [TarFileName]),
    │ │ │ │ -    make_tar(RelFileName,SystoolsOpts),
    │ │ │ │ +    io:fwrite("Creating tar file ~ts ...~n", [TarFileName]),
    │ │ │ │ +    make_tar(RelFileName,SystoolsOpts),
    │ │ │ │  
    │ │ │ │ -    TmpDir = filename:join(Dir,"tmp"),
    │ │ │ │ -    io:fwrite("Creating directory ~tp ...~n",[TmpDir]),
    │ │ │ │ -    file:make_dir(TmpDir),
    │ │ │ │ -
    │ │ │ │ -    io:fwrite("Extracting ~ts into directory ~ts ...~n", [TarFileName,TmpDir]),
    │ │ │ │ -    extract_tar(TarFileName, TmpDir),
    │ │ │ │ -
    │ │ │ │ -    TmpBinDir = filename:join([TmpDir, "bin"]),
    │ │ │ │ -    ErtsBinDir = filename:join([TmpDir, "erts-" ++ ErtsVsn, "bin"]),
    │ │ │ │ -    io:fwrite("Deleting \"erl\" and \"start\" in directory ~ts ...~n",
    │ │ │ │ -              [ErtsBinDir]),
    │ │ │ │ -    file:delete(filename:join([ErtsBinDir, "erl"])),
    │ │ │ │ -    file:delete(filename:join([ErtsBinDir, "start"])),
    │ │ │ │ -
    │ │ │ │ -    io:fwrite("Creating temporary directory ~ts ...~n", [TmpBinDir]),
    │ │ │ │ -    file:make_dir(TmpBinDir),
    │ │ │ │ -
    │ │ │ │ -    io:fwrite("Copying file \"~ts.boot\" to ~ts ...~n",
    │ │ │ │ -              [PlainRelFileName, filename:join([TmpBinDir, "start.boot"])]),
    │ │ │ │ -    copy_file(PlainRelFileName++".boot",filename:join([TmpBinDir, "start.boot"])),
    │ │ │ │ +    TmpDir = filename:join(Dir,"tmp"),
    │ │ │ │ +    io:fwrite("Creating directory ~tp ...~n",[TmpDir]),
    │ │ │ │ +    file:make_dir(TmpDir),
    │ │ │ │ +
    │ │ │ │ +    io:fwrite("Extracting ~ts into directory ~ts ...~n", [TarFileName,TmpDir]),
    │ │ │ │ +    extract_tar(TarFileName, TmpDir),
    │ │ │ │ +
    │ │ │ │ +    TmpBinDir = filename:join([TmpDir, "bin"]),
    │ │ │ │ +    ErtsBinDir = filename:join([TmpDir, "erts-" ++ ErtsVsn, "bin"]),
    │ │ │ │ +    io:fwrite("Deleting \"erl\" and \"start\" in directory ~ts ...~n",
    │ │ │ │ +              [ErtsBinDir]),
    │ │ │ │ +    file:delete(filename:join([ErtsBinDir, "erl"])),
    │ │ │ │ +    file:delete(filename:join([ErtsBinDir, "start"])),
    │ │ │ │ +
    │ │ │ │ +    io:fwrite("Creating temporary directory ~ts ...~n", [TmpBinDir]),
    │ │ │ │ +    file:make_dir(TmpBinDir),
    │ │ │ │ +
    │ │ │ │ +    io:fwrite("Copying file \"~ts.boot\" to ~ts ...~n",
    │ │ │ │ +              [PlainRelFileName, filename:join([TmpBinDir, "start.boot"])]),
    │ │ │ │ +    copy_file(PlainRelFileName++".boot",filename:join([TmpBinDir, "start.boot"])),
    │ │ │ │  
    │ │ │ │ -    io:fwrite("Copying files \"epmd\", \"run_erl\" and \"to_erl\" from \n"
    │ │ │ │ +    io:fwrite("Copying files \"epmd\", \"run_erl\" and \"to_erl\" from \n"
    │ │ │ │                "~ts to ~ts ...~n",
    │ │ │ │ -              [ErtsBinDir, TmpBinDir]),
    │ │ │ │ -    copy_file(filename:join([ErtsBinDir, "epmd"]),
    │ │ │ │ -              filename:join([TmpBinDir, "epmd"]), [preserve]),
    │ │ │ │ -    copy_file(filename:join([ErtsBinDir, "run_erl"]),
    │ │ │ │ -              filename:join([TmpBinDir, "run_erl"]), [preserve]),
    │ │ │ │ -    copy_file(filename:join([ErtsBinDir, "to_erl"]),
    │ │ │ │ -              filename:join([TmpBinDir, "to_erl"]), [preserve]),
    │ │ │ │ +              [ErtsBinDir, TmpBinDir]),
    │ │ │ │ +    copy_file(filename:join([ErtsBinDir, "epmd"]),
    │ │ │ │ +              filename:join([TmpBinDir, "epmd"]), [preserve]),
    │ │ │ │ +    copy_file(filename:join([ErtsBinDir, "run_erl"]),
    │ │ │ │ +              filename:join([TmpBinDir, "run_erl"]), [preserve]),
    │ │ │ │ +    copy_file(filename:join([ErtsBinDir, "to_erl"]),
    │ │ │ │ +              filename:join([TmpBinDir, "to_erl"]), [preserve]),
    │ │ │ │  
    │ │ │ │      %% This is needed if 'start' script created from 'start.src' shall
    │ │ │ │      %% be used as it points out this directory as log dir for 'run_erl'
    │ │ │ │ -    TmpLogDir = filename:join([TmpDir, "log"]),
    │ │ │ │ -    io:fwrite("Creating temporary directory ~ts ...~n", [TmpLogDir]),
    │ │ │ │ -    ok = file:make_dir(TmpLogDir),
    │ │ │ │ -
    │ │ │ │ -    StartErlDataFile = filename:join([TmpDir, "releases", "start_erl.data"]),
    │ │ │ │ -    io:fwrite("Creating ~ts ...~n", [StartErlDataFile]),
    │ │ │ │ -    StartErlData = io_lib:fwrite("~s ~s~n", [ErtsVsn, RelVsn]),
    │ │ │ │ -    write_file(StartErlDataFile, StartErlData),
    │ │ │ │ -
    │ │ │ │ -    io:fwrite("Recreating tar file ~ts from contents in directory ~ts ...~n",
    │ │ │ │ -	      [TarFileName,TmpDir]),
    │ │ │ │ -    {ok, Tar} = erl_tar:open(TarFileName, [write, compressed]),
    │ │ │ │ +    TmpLogDir = filename:join([TmpDir, "log"]),
    │ │ │ │ +    io:fwrite("Creating temporary directory ~ts ...~n", [TmpLogDir]),
    │ │ │ │ +    ok = file:make_dir(TmpLogDir),
    │ │ │ │ +
    │ │ │ │ +    StartErlDataFile = filename:join([TmpDir, "releases", "start_erl.data"]),
    │ │ │ │ +    io:fwrite("Creating ~ts ...~n", [StartErlDataFile]),
    │ │ │ │ +    StartErlData = io_lib:fwrite("~s ~s~n", [ErtsVsn, RelVsn]),
    │ │ │ │ +    write_file(StartErlDataFile, StartErlData),
    │ │ │ │ +
    │ │ │ │ +    io:fwrite("Recreating tar file ~ts from contents in directory ~ts ...~n",
    │ │ │ │ +	      [TarFileName,TmpDir]),
    │ │ │ │ +    {ok, Tar} = erl_tar:open(TarFileName, [write, compressed]),
    │ │ │ │      %% {ok, Cwd} = file:get_cwd(),
    │ │ │ │      %% file:set_cwd("tmp"),
    │ │ │ │      ErtsDir = "erts-"++ErtsVsn,
    │ │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"bin"), "bin", []),
    │ │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,ErtsDir), ErtsDir, []),
    │ │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"releases"), "releases", []),
    │ │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"lib"), "lib", []),
    │ │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"log"), "log", []),
    │ │ │ │ -    erl_tar:close(Tar),
    │ │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"bin"), "bin", []),
    │ │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,ErtsDir), ErtsDir, []),
    │ │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"releases"), "releases", []),
    │ │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"lib"), "lib", []),
    │ │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"log"), "log", []),
    │ │ │ │ +    erl_tar:close(Tar),
    │ │ │ │      %% file:set_cwd(Cwd),
    │ │ │ │ -    io:fwrite("Removing directory ~ts ...~n",[TmpDir]),
    │ │ │ │ -    remove_dir_tree(TmpDir),
    │ │ │ │ +    io:fwrite("Removing directory ~ts ...~n",[TmpDir]),
    │ │ │ │ +    remove_dir_tree(TmpDir),
    │ │ │ │      ok.
    │ │ │ │  
    │ │ │ │  
    │ │ │ │ -install(RelFileName, RootDir) ->
    │ │ │ │ +install(RelFileName, RootDir) ->
    │ │ │ │      TarFile = RelFileName ++ ".tar.gz",
    │ │ │ │ -    io:fwrite("Extracting ~ts ...~n", [TarFile]),
    │ │ │ │ -    extract_tar(TarFile, RootDir),
    │ │ │ │ -    StartErlDataFile = filename:join([RootDir, "releases", "start_erl.data"]),
    │ │ │ │ -    {ok, StartErlData} = read_txt_file(StartErlDataFile),
    │ │ │ │ -    [ErlVsn, _RelVsn| _] = string:tokens(StartErlData, " \n"),
    │ │ │ │ -    ErtsBinDir = filename:join([RootDir, "erts-" ++ ErlVsn, "bin"]),
    │ │ │ │ -    BinDir = filename:join([RootDir, "bin"]),
    │ │ │ │ -    io:fwrite("Substituting in erl.src, start.src and start_erl.src to "
    │ │ │ │ -              "form erl, start and start_erl ...\n"),
    │ │ │ │ -    subst_src_scripts(["erl", "start", "start_erl"], ErtsBinDir, BinDir,
    │ │ │ │ -                      [{"FINAL_ROOTDIR", RootDir}, {"EMU", "beam"}],
    │ │ │ │ -                      [preserve]),
    │ │ │ │ +    io:fwrite("Extracting ~ts ...~n", [TarFile]),
    │ │ │ │ +    extract_tar(TarFile, RootDir),
    │ │ │ │ +    StartErlDataFile = filename:join([RootDir, "releases", "start_erl.data"]),
    │ │ │ │ +    {ok, StartErlData} = read_txt_file(StartErlDataFile),
    │ │ │ │ +    [ErlVsn, _RelVsn| _] = string:tokens(StartErlData, " \n"),
    │ │ │ │ +    ErtsBinDir = filename:join([RootDir, "erts-" ++ ErlVsn, "bin"]),
    │ │ │ │ +    BinDir = filename:join([RootDir, "bin"]),
    │ │ │ │ +    io:fwrite("Substituting in erl.src, start.src and start_erl.src to "
    │ │ │ │ +              "form erl, start and start_erl ...\n"),
    │ │ │ │ +    subst_src_scripts(["erl", "start", "start_erl"], ErtsBinDir, BinDir,
    │ │ │ │ +                      [{"FINAL_ROOTDIR", RootDir}, {"EMU", "beam"}],
    │ │ │ │ +                      [preserve]),
    │ │ │ │      %%! Workaround for pre OTP 17.0: start.src and start_erl.src did
    │ │ │ │      %%! not have correct permissions, so the above 'preserve' option did not help
    │ │ │ │ -    ok = file:change_mode(filename:join(BinDir,"start"),8#0755),
    │ │ │ │ -    ok = file:change_mode(filename:join(BinDir,"start_erl"),8#0755),
    │ │ │ │ +    ok = file:change_mode(filename:join(BinDir,"start"),8#0755),
    │ │ │ │ +    ok = file:change_mode(filename:join(BinDir,"start_erl"),8#0755),
    │ │ │ │  
    │ │ │ │ -    io:fwrite("Creating the RELEASES file ...\n"),
    │ │ │ │ -    create_RELEASES(RootDir, filename:join([RootDir, "releases",
    │ │ │ │ -					    filename:basename(RelFileName)])).
    │ │ │ │ +    io:fwrite("Creating the RELEASES file ...\n"),
    │ │ │ │ +    create_RELEASES(RootDir, filename:join([RootDir, "releases",
    │ │ │ │ +					    filename:basename(RelFileName)])).
    │ │ │ │  
    │ │ │ │  %% LOCALS
    │ │ │ │  
    │ │ │ │  %% make_script(RelFileName,Opts)
    │ │ │ │  %%
    │ │ │ │ -make_script(RelFileName,Opts) ->
    │ │ │ │ -    systools:make_script(RelFileName, [no_module_tests,
    │ │ │ │ -				       {outdir,filename:dirname(RelFileName)}
    │ │ │ │ -				       |Opts]).
    │ │ │ │ +make_script(RelFileName,Opts) ->
    │ │ │ │ +    systools:make_script(RelFileName, [no_module_tests,
    │ │ │ │ +				       {outdir,filename:dirname(RelFileName)}
    │ │ │ │ +				       |Opts]).
    │ │ │ │  
    │ │ │ │  %% make_tar(RelFileName,Opts)
    │ │ │ │  %%
    │ │ │ │ -make_tar(RelFileName,Opts) ->
    │ │ │ │ -    RootDir = code:root_dir(),
    │ │ │ │ -    systools:make_tar(RelFileName, [{erts, RootDir},
    │ │ │ │ -				    {outdir,filename:dirname(RelFileName)}
    │ │ │ │ -				    |Opts]).
    │ │ │ │ +make_tar(RelFileName,Opts) ->
    │ │ │ │ +    RootDir = code:root_dir(),
    │ │ │ │ +    systools:make_tar(RelFileName, [{erts, RootDir},
    │ │ │ │ +				    {outdir,filename:dirname(RelFileName)}
    │ │ │ │ +				    |Opts]).
    │ │ │ │  
    │ │ │ │  %% extract_tar(TarFile, DestDir)
    │ │ │ │  %%
    │ │ │ │ -extract_tar(TarFile, DestDir) ->
    │ │ │ │ -    erl_tar:extract(TarFile, [{cwd, DestDir}, compressed]).
    │ │ │ │ +extract_tar(TarFile, DestDir) ->
    │ │ │ │ +    erl_tar:extract(TarFile, [{cwd, DestDir}, compressed]).
    │ │ │ │  
    │ │ │ │ -create_RELEASES(DestDir, RelFileName) ->
    │ │ │ │ -    release_handler:create_RELEASES(DestDir, RelFileName ++ ".rel").
    │ │ │ │ +create_RELEASES(DestDir, RelFileName) ->
    │ │ │ │ +    release_handler:create_RELEASES(DestDir, RelFileName ++ ".rel").
    │ │ │ │  
    │ │ │ │ -subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) ->
    │ │ │ │ -    lists:foreach(fun(Script) ->
    │ │ │ │ -                          subst_src_script(Script, SrcDir, DestDir,
    │ │ │ │ -                                           Vars, Opts)
    │ │ │ │ -                  end, Scripts).
    │ │ │ │ -
    │ │ │ │ -subst_src_script(Script, SrcDir, DestDir, Vars, Opts) ->
    │ │ │ │ -    subst_file(filename:join([SrcDir, Script ++ ".src"]),
    │ │ │ │ -               filename:join([DestDir, Script]),
    │ │ │ │ -               Vars, Opts).
    │ │ │ │ -
    │ │ │ │ -subst_file(Src, Dest, Vars, Opts) ->
    │ │ │ │ -    {ok, Conts} = read_txt_file(Src),
    │ │ │ │ -    NConts = subst(Conts, Vars),
    │ │ │ │ -    write_file(Dest, NConts),
    │ │ │ │ -    case lists:member(preserve, Opts) of
    │ │ │ │ +subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) ->
    │ │ │ │ +    lists:foreach(fun(Script) ->
    │ │ │ │ +                          subst_src_script(Script, SrcDir, DestDir,
    │ │ │ │ +                                           Vars, Opts)
    │ │ │ │ +                  end, Scripts).
    │ │ │ │ +
    │ │ │ │ +subst_src_script(Script, SrcDir, DestDir, Vars, Opts) ->
    │ │ │ │ +    subst_file(filename:join([SrcDir, Script ++ ".src"]),
    │ │ │ │ +               filename:join([DestDir, Script]),
    │ │ │ │ +               Vars, Opts).
    │ │ │ │ +
    │ │ │ │ +subst_file(Src, Dest, Vars, Opts) ->
    │ │ │ │ +    {ok, Conts} = read_txt_file(Src),
    │ │ │ │ +    NConts = subst(Conts, Vars),
    │ │ │ │ +    write_file(Dest, NConts),
    │ │ │ │ +    case lists:member(preserve, Opts) of
    │ │ │ │          true ->
    │ │ │ │ -            {ok, FileInfo} = file:read_file_info(Src),
    │ │ │ │ -            file:write_file_info(Dest, FileInfo);
    │ │ │ │ +            {ok, FileInfo} = file:read_file_info(Src),
    │ │ │ │ +            file:write_file_info(Dest, FileInfo);
    │ │ │ │          false ->
    │ │ │ │              ok
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │  %% subst(Str, Vars)
    │ │ │ │  %% Vars = [{Var, Val}]
    │ │ │ │  %% Var = Val = string()
    │ │ │ │  %% Substitute all occurrences of %Var% for Val in Str, using the list
    │ │ │ │  %% of variables in Vars.
    │ │ │ │  %%
    │ │ │ │ -subst(Str, Vars) ->
    │ │ │ │ -    subst(Str, Vars, []).
    │ │ │ │ +subst(Str, Vars) ->
    │ │ │ │ +    subst(Str, Vars, []).
    │ │ │ │  
    │ │ │ │ -subst([$%, C| Rest], Vars, Result) when $A =< C, C =< $Z ->
    │ │ │ │ -    subst_var([C| Rest], Vars, Result, []);
    │ │ │ │ -subst([$%, C| Rest], Vars, Result) when $a =< C, C =< $z ->
    │ │ │ │ -    subst_var([C| Rest], Vars, Result, []);
    │ │ │ │ -subst([$%, C| Rest], Vars, Result) when  C == $_ ->
    │ │ │ │ -    subst_var([C| Rest], Vars, Result, []);
    │ │ │ │ -subst([C| Rest], Vars, Result) ->
    │ │ │ │ -    subst(Rest, Vars, [C| Result]);
    │ │ │ │ -subst([], _Vars, Result) ->
    │ │ │ │ -    lists:reverse(Result).
    │ │ │ │ -
    │ │ │ │ -subst_var([$%| Rest], Vars, Result, VarAcc) ->
    │ │ │ │ -    Key = lists:reverse(VarAcc),
    │ │ │ │ -    case lists:keysearch(Key, 1, Vars) of
    │ │ │ │ -        {value, {Key, Value}} ->
    │ │ │ │ -            subst(Rest, Vars, lists:reverse(Value, Result));
    │ │ │ │ +subst([$%, C| Rest], Vars, Result) when $A =< C, C =< $Z ->
    │ │ │ │ +    subst_var([C| Rest], Vars, Result, []);
    │ │ │ │ +subst([$%, C| Rest], Vars, Result) when $a =< C, C =< $z ->
    │ │ │ │ +    subst_var([C| Rest], Vars, Result, []);
    │ │ │ │ +subst([$%, C| Rest], Vars, Result) when  C == $_ ->
    │ │ │ │ +    subst_var([C| Rest], Vars, Result, []);
    │ │ │ │ +subst([C| Rest], Vars, Result) ->
    │ │ │ │ +    subst(Rest, Vars, [C| Result]);
    │ │ │ │ +subst([], _Vars, Result) ->
    │ │ │ │ +    lists:reverse(Result).
    │ │ │ │ +
    │ │ │ │ +subst_var([$%| Rest], Vars, Result, VarAcc) ->
    │ │ │ │ +    Key = lists:reverse(VarAcc),
    │ │ │ │ +    case lists:keysearch(Key, 1, Vars) of
    │ │ │ │ +        {value, {Key, Value}} ->
    │ │ │ │ +            subst(Rest, Vars, lists:reverse(Value, Result));
    │ │ │ │          false ->
    │ │ │ │ -            subst(Rest, Vars, [$%| VarAcc ++ [$%| Result]])
    │ │ │ │ +            subst(Rest, Vars, [$%| VarAcc ++ [$%| Result]])
    │ │ │ │      end;
    │ │ │ │ -subst_var([C| Rest], Vars, Result, VarAcc) ->
    │ │ │ │ -    subst_var(Rest, Vars, Result, [C| VarAcc]);
    │ │ │ │ -subst_var([], Vars, Result, VarAcc) ->
    │ │ │ │ -    subst([], Vars, [VarAcc ++ [$%| Result]]).
    │ │ │ │ -
    │ │ │ │ -copy_file(Src, Dest) ->
    │ │ │ │ -    copy_file(Src, Dest, []).
    │ │ │ │ -
    │ │ │ │ -copy_file(Src, Dest, Opts) ->
    │ │ │ │ -    {ok,_} = file:copy(Src, Dest),
    │ │ │ │ -    case lists:member(preserve, Opts) of
    │ │ │ │ +subst_var([C| Rest], Vars, Result, VarAcc) ->
    │ │ │ │ +    subst_var(Rest, Vars, Result, [C| VarAcc]);
    │ │ │ │ +subst_var([], Vars, Result, VarAcc) ->
    │ │ │ │ +    subst([], Vars, [VarAcc ++ [$%| Result]]).
    │ │ │ │ +
    │ │ │ │ +copy_file(Src, Dest) ->
    │ │ │ │ +    copy_file(Src, Dest, []).
    │ │ │ │ +
    │ │ │ │ +copy_file(Src, Dest, Opts) ->
    │ │ │ │ +    {ok,_} = file:copy(Src, Dest),
    │ │ │ │ +    case lists:member(preserve, Opts) of
    │ │ │ │          true ->
    │ │ │ │ -            {ok, FileInfo} = file:read_file_info(Src),
    │ │ │ │ -            file:write_file_info(Dest, FileInfo);
    │ │ │ │ +            {ok, FileInfo} = file:read_file_info(Src),
    │ │ │ │ +            file:write_file_info(Dest, FileInfo);
    │ │ │ │          false ->
    │ │ │ │              ok
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -write_file(FName, Conts) ->
    │ │ │ │ -    Enc = file:native_name_encoding(),
    │ │ │ │ -    {ok, Fd} = file:open(FName, [write]),
    │ │ │ │ -    file:write(Fd, unicode:characters_to_binary(Conts,Enc,Enc)),
    │ │ │ │ -    file:close(Fd).
    │ │ │ │ -
    │ │ │ │ -read_txt_file(File) ->
    │ │ │ │ -    {ok, Bin} = file:read_file(File),
    │ │ │ │ -    {ok, binary_to_list(Bin)}.
    │ │ │ │ -
    │ │ │ │ -remove_dir_tree(Dir) ->
    │ │ │ │ -    remove_all_files(".", [Dir]).
    │ │ │ │ -
    │ │ │ │ -remove_all_files(Dir, Files) ->
    │ │ │ │ -    lists:foreach(fun(File) ->
    │ │ │ │ -                          FilePath = filename:join([Dir, File]),
    │ │ │ │ -                          case filelib:is_dir(FilePath) of
    │ │ │ │ +write_file(FName, Conts) ->
    │ │ │ │ +    Enc = file:native_name_encoding(),
    │ │ │ │ +    {ok, Fd} = file:open(FName, [write]),
    │ │ │ │ +    file:write(Fd, unicode:characters_to_binary(Conts,Enc,Enc)),
    │ │ │ │ +    file:close(Fd).
    │ │ │ │ +
    │ │ │ │ +read_txt_file(File) ->
    │ │ │ │ +    {ok, Bin} = file:read_file(File),
    │ │ │ │ +    {ok, binary_to_list(Bin)}.
    │ │ │ │ +
    │ │ │ │ +remove_dir_tree(Dir) ->
    │ │ │ │ +    remove_all_files(".", [Dir]).
    │ │ │ │ +
    │ │ │ │ +remove_all_files(Dir, Files) ->
    │ │ │ │ +    lists:foreach(fun(File) ->
    │ │ │ │ +                          FilePath = filename:join([Dir, File]),
    │ │ │ │ +                          case filelib:is_dir(FilePath) of
    │ │ │ │                                true ->
    │ │ │ │ -                                  {ok, DirFiles} = file:list_dir(FilePath),
    │ │ │ │ -                                  remove_all_files(FilePath, DirFiles),
    │ │ │ │ -                                  file:del_dir(FilePath);
    │ │ │ │ +                                  {ok, DirFiles} = file:list_dir(FilePath),
    │ │ │ │ +                                  remove_all_files(FilePath, DirFiles),
    │ │ │ │ +                                  file:del_dir(FilePath);
    │ │ │ │                                _ ->
    │ │ │ │ -                                  file:delete(FilePath)
    │ │ │ │ +                                  file:delete(FilePath)
    │ │ │ │                            end
    │ │ │ │ -                  end, Files).
    │ │ │ │ + end, Files).
    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/content.opf │ │ │ │ ├── OEBPS/content.opf │ │ │ │ │ @@ -1,14 +1,14 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Erlang System Documentation - 27.3.4.11 │ │ │ │ │ - urn:uuid:d84b0db7-01db-0af1-135f-84af594bbae6 │ │ │ │ │ + urn:uuid:8959efea-3128-ef7e-9e49-5a90ca598aca │ │ │ │ │ en │ │ │ │ │ - 2026-05-05T13:52:08Z │ │ │ │ │ + 2026-05-10T23:47:21Z │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -82,21 +82,21 @@ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ - │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ - │ │ │ │ │ │ │ │ │ │ + │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/conc_prog.xhtml │ │ │ │ @@ -37,107 +37,107 @@ │ │ │ │ threads of execution in an Erlang program and to allow these threads to │ │ │ │ communicate with each other. In Erlang, each thread of execution is called a │ │ │ │ process.

    (Aside: the term "process" is usually used when the threads of execution share │ │ │ │ no data with each other and the term "thread" when they share data in some way. │ │ │ │ Threads of execution in Erlang share no data, that is why they are called │ │ │ │ processes).

    The Erlang BIF spawn is used to create a new process: │ │ │ │ spawn(Module, Exported_Function, List of Arguments). Consider the following │ │ │ │ -module:

    -module(tut14).
    │ │ │ │ +module:

    -module(tut14).
    │ │ │ │  
    │ │ │ │ --export([start/0, say_something/2]).
    │ │ │ │ +-export([start/0, say_something/2]).
    │ │ │ │  
    │ │ │ │ -say_something(What, 0) ->
    │ │ │ │ +say_something(What, 0) ->
    │ │ │ │      done;
    │ │ │ │ -say_something(What, Times) ->
    │ │ │ │ -    io:format("~p~n", [What]),
    │ │ │ │ -    say_something(What, Times - 1).
    │ │ │ │ -
    │ │ │ │ -start() ->
    │ │ │ │ -    spawn(tut14, say_something, [hello, 3]),
    │ │ │ │ -    spawn(tut14, say_something, [goodbye, 3]).
    5> c(tut14).
    │ │ │ │ -{ok,tut14}
    │ │ │ │ -6> tut14:say_something(hello, 3).
    │ │ │ │ +say_something(What, Times) ->
    │ │ │ │ +    io:format("~p~n", [What]),
    │ │ │ │ +    say_something(What, Times - 1).
    │ │ │ │ +
    │ │ │ │ +start() ->
    │ │ │ │ +    spawn(tut14, say_something, [hello, 3]),
    │ │ │ │ +    spawn(tut14, say_something, [goodbye, 3]).
    5> c(tut14).
    │ │ │ │ +{ok,tut14}
    │ │ │ │ +6> tut14:say_something(hello, 3).
    │ │ │ │  hello
    │ │ │ │  hello
    │ │ │ │  hello
    │ │ │ │  done

    As shown, the function say_something writes its first argument the number of │ │ │ │ times specified by second argument. The function start starts two Erlang │ │ │ │ processes, one that writes "hello" three times and one that writes "goodbye" │ │ │ │ three times. Both processes use the function say_something. Notice that a │ │ │ │ function used in this way by spawn, to start a process, must be exported from │ │ │ │ -the module (that is, in the -export at the start of the module).

    9> tut14:start().
    │ │ │ │ +the module (that is, in the -export at the start of the module).

    9> tut14:start().
    │ │ │ │  hello
    │ │ │ │  goodbye
    │ │ │ │  <0.63.0>
    │ │ │ │  hello
    │ │ │ │  goodbye
    │ │ │ │  hello
    │ │ │ │  goodbye

    Notice that it did not write "hello" three times and then "goodbye" three times. │ │ │ │ Instead, the first process wrote a "hello", the second a "goodbye", the first │ │ │ │ another "hello" and so forth. But where did the <0.63.0> come from? The return │ │ │ │ value of a function is the return value of the last "thing" in the function. The │ │ │ │ -last thing in the function start is

    spawn(tut14, say_something, [goodbye, 3]).

    spawn returns a process identifier, or pid, which uniquely identifies the │ │ │ │ +last thing in the function start is

    spawn(tut14, say_something, [goodbye, 3]).

    spawn returns a process identifier, or pid, which uniquely identifies the │ │ │ │ process. So <0.63.0> is the pid of the spawn function call above. The next │ │ │ │ example shows how to use pids.

    Notice also that ~p is used instead of ~w in io:format/2. To quote the manual:

    ~p Writes the data with standard syntax in the same way as ~w, but breaks terms │ │ │ │ whose printed representation is longer than one line into many lines and indents │ │ │ │ each line sensibly. It also tries to detect flat lists of printable characters and │ │ │ │ to output these as strings

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Message Passing │ │ │ │

    │ │ │ │

    In the following example two processes are created and they send messages to │ │ │ │ -each other a number of times.

    -module(tut15).
    │ │ │ │ +each other a number of times.

    -module(tut15).
    │ │ │ │  
    │ │ │ │ --export([start/0, ping/2, pong/0]).
    │ │ │ │ +-export([start/0, ping/2, pong/0]).
    │ │ │ │  
    │ │ │ │ -ping(0, Pong_PID) ->
    │ │ │ │ +ping(0, Pong_PID) ->
    │ │ │ │      Pong_PID ! finished,
    │ │ │ │ -    io:format("ping finished~n", []);
    │ │ │ │ +    io:format("ping finished~n", []);
    │ │ │ │  
    │ │ │ │ -ping(N, Pong_PID) ->
    │ │ │ │ -    Pong_PID ! {ping, self()},
    │ │ │ │ +ping(N, Pong_PID) ->
    │ │ │ │ +    Pong_PID ! {ping, self()},
    │ │ │ │      receive
    │ │ │ │          pong ->
    │ │ │ │ -            io:format("Ping received pong~n", [])
    │ │ │ │ +            io:format("Ping received pong~n", [])
    │ │ │ │      end,
    │ │ │ │ -    ping(N - 1, Pong_PID).
    │ │ │ │ +    ping(N - 1, Pong_PID).
    │ │ │ │  
    │ │ │ │ -pong() ->
    │ │ │ │ +pong() ->
    │ │ │ │      receive
    │ │ │ │          finished ->
    │ │ │ │ -            io:format("Pong finished~n", []);
    │ │ │ │ -        {ping, Ping_PID} ->
    │ │ │ │ -            io:format("Pong received ping~n", []),
    │ │ │ │ +            io:format("Pong finished~n", []);
    │ │ │ │ +        {ping, Ping_PID} ->
    │ │ │ │ +            io:format("Pong received ping~n", []),
    │ │ │ │              Ping_PID ! pong,
    │ │ │ │ -            pong()
    │ │ │ │ +            pong()
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -start() ->
    │ │ │ │ -    Pong_PID = spawn(tut15, pong, []),
    │ │ │ │ -    spawn(tut15, ping, [3, Pong_PID]).
    1> c(tut15).
    │ │ │ │ -{ok,tut15}
    │ │ │ │ -2> tut15: start().
    │ │ │ │ +start() ->
    │ │ │ │ +    Pong_PID = spawn(tut15, pong, []),
    │ │ │ │ +    spawn(tut15, ping, [3, Pong_PID]).
    1> c(tut15).
    │ │ │ │ +{ok,tut15}
    │ │ │ │ +2> tut15: start().
    │ │ │ │  <0.36.0>
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  ping finished
    │ │ │ │ -Pong finished

    The function start first creates a process, let us call it "pong":

    Pong_PID = spawn(tut15, pong, [])

    This process executes tut15:pong(). Pong_PID is the process identity of the │ │ │ │ -"pong" process. The function start now creates another process "ping":

    spawn(tut15, ping, [3, Pong_PID]),

    This process executes:

    tut15:ping(3, Pong_PID)

    <0.36.0> is the return value from the start function.

    The process "pong" now does:

    receive
    │ │ │ │ +Pong finished

    The function start first creates a process, let us call it "pong":

    Pong_PID = spawn(tut15, pong, [])

    This process executes tut15:pong(). Pong_PID is the process identity of the │ │ │ │ +"pong" process. The function start now creates another process "ping":

    spawn(tut15, ping, [3, Pong_PID]),

    This process executes:

    tut15:ping(3, Pong_PID)

    <0.36.0> is the return value from the start function.

    The process "pong" now does:

    receive
    │ │ │ │      finished ->
    │ │ │ │ -        io:format("Pong finished~n", []);
    │ │ │ │ -    {ping, Ping_PID} ->
    │ │ │ │ -        io:format("Pong received ping~n", []),
    │ │ │ │ +        io:format("Pong finished~n", []);
    │ │ │ │ +    {ping, Ping_PID} ->
    │ │ │ │ +        io:format("Pong received ping~n", []),
    │ │ │ │          Ping_PID ! pong,
    │ │ │ │ -        pong()
    │ │ │ │ +        pong()
    │ │ │ │  end.

    The receive construct is used to allow processes to wait for messages from │ │ │ │ other processes. It has the following format:

    receive
    │ │ │ │     pattern1 ->
    │ │ │ │         actions1;
    │ │ │ │     pattern2 ->
    │ │ │ │         actions2;
    │ │ │ │     ....
    │ │ │ │ @@ -158,84 +158,84 @@
    │ │ │ │  queue (keeping the first message and any other messages in the queue). If the
    │ │ │ │  second message does not match, the third message is tried, and so on, until the
    │ │ │ │  end of the queue is reached. If the end of the queue is reached, the process
    │ │ │ │  blocks (stops execution) and waits until a new message is received and this
    │ │ │ │  procedure is repeated.

    The Erlang implementation is "clever" and minimizes the number of times each │ │ │ │ message is tested against the patterns in each receive.

    Now back to the ping pong example.

    "Pong" is waiting for messages. If the atom finished is received, "pong" │ │ │ │ writes "Pong finished" to the output and, as it has nothing more to do, │ │ │ │ -terminates. If it receives a message with the format:

    {ping, Ping_PID}

    it writes "Pong received ping" to the output and sends the atom pong to the │ │ │ │ +terminates. If it receives a message with the format:

    {ping, Ping_PID}

    it writes "Pong received ping" to the output and sends the atom pong to the │ │ │ │ process "ping":

    Ping_PID ! pong

    Notice how the operator "!" is used to send messages. The syntax of "!" is:

    Pid ! Message

    That is, Message (any Erlang term) is sent to the process with identity Pid.

    After sending the message pong to the process "ping", "pong" calls the pong │ │ │ │ function again, which causes it to get back to the receive again and wait for │ │ │ │ -another message.

    Now let us look at the process "ping". Recall that it was started by executing:

    tut15:ping(3, Pong_PID)

    Looking at the function ping/2, the second clause of ping/2 is executed │ │ │ │ +another message.

    Now let us look at the process "ping". Recall that it was started by executing:

    tut15:ping(3, Pong_PID)

    Looking at the function ping/2, the second clause of ping/2 is executed │ │ │ │ since the value of the first argument is 3 (not 0) (first clause head is │ │ │ │ -ping(0,Pong_PID), second clause head is ping(N,Pong_PID), so N becomes 3).

    The second clause sends a message to "pong":

    Pong_PID ! {ping, self()},

    self/0 returns the pid of the process that executes self/0, in this case the │ │ │ │ +ping(0,Pong_PID), second clause head is ping(N,Pong_PID), so N becomes 3).

    The second clause sends a message to "pong":

    Pong_PID ! {ping, self()},

    self/0 returns the pid of the process that executes self/0, in this case the │ │ │ │ pid of "ping". (Recall the code for "pong", this lands up in the variable │ │ │ │ Ping_PID in the receive previously explained.)

    "Ping" now waits for a reply from "pong":

    receive
    │ │ │ │      pong ->
    │ │ │ │ -        io:format("Ping received pong~n", [])
    │ │ │ │ +        io:format("Ping received pong~n", [])
    │ │ │ │  end,

    It writes "Ping received pong" when this reply arrives, after which "ping" calls │ │ │ │ -the ping function again.

    ping(N - 1, Pong_PID)

    N-1 causes the first argument to be decremented until it becomes 0. When this │ │ │ │ -occurs, the first clause of ping/2 is executed:

    ping(0, Pong_PID) ->
    │ │ │ │ +the ping function again.

    ping(N - 1, Pong_PID)

    N-1 causes the first argument to be decremented until it becomes 0. When this │ │ │ │ +occurs, the first clause of ping/2 is executed:

    ping(0, Pong_PID) ->
    │ │ │ │      Pong_PID !  finished,
    │ │ │ │ -    io:format("ping finished~n", []);

    The atom finished is sent to "pong" (causing it to terminate as described │ │ │ │ + io:format("ping finished~n", []);

    The atom finished is sent to "pong" (causing it to terminate as described │ │ │ │ above) and "ping finished" is written to the output. "Ping" then terminates as │ │ │ │ it has nothing left to do.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Registered Process Names │ │ │ │

    │ │ │ │

    In the above example, "pong" was first created to be able to give the identity │ │ │ │ of "pong" when "ping" was started. That is, in some way "ping" must be able to │ │ │ │ know the identity of "pong" to be able to send a message to it. Sometimes │ │ │ │ processes which need to know each other's identities are started independently │ │ │ │ of each other. Erlang thus provides a mechanism for processes to be given names │ │ │ │ so that these names can be used as identities instead of pids. This is done by │ │ │ │ -using the register BIF:

    register(some_atom, Pid)

    Let us now rewrite the ping pong example using this and give the name pong to │ │ │ │ -the "pong" process:

    -module(tut16).
    │ │ │ │ +using the register BIF:

    register(some_atom, Pid)

    Let us now rewrite the ping pong example using this and give the name pong to │ │ │ │ +the "pong" process:

    -module(tut16).
    │ │ │ │  
    │ │ │ │ --export([start/0, ping/1, pong/0]).
    │ │ │ │ +-export([start/0, ping/1, pong/0]).
    │ │ │ │  
    │ │ │ │ -ping(0) ->
    │ │ │ │ +ping(0) ->
    │ │ │ │      pong ! finished,
    │ │ │ │ -    io:format("ping finished~n", []);
    │ │ │ │ +    io:format("ping finished~n", []);
    │ │ │ │  
    │ │ │ │ -ping(N) ->
    │ │ │ │ -    pong ! {ping, self()},
    │ │ │ │ +ping(N) ->
    │ │ │ │ +    pong ! {ping, self()},
    │ │ │ │      receive
    │ │ │ │          pong ->
    │ │ │ │ -            io:format("Ping received pong~n", [])
    │ │ │ │ +            io:format("Ping received pong~n", [])
    │ │ │ │      end,
    │ │ │ │ -    ping(N - 1).
    │ │ │ │ +    ping(N - 1).
    │ │ │ │  
    │ │ │ │ -pong() ->
    │ │ │ │ +pong() ->
    │ │ │ │      receive
    │ │ │ │          finished ->
    │ │ │ │ -            io:format("Pong finished~n", []);
    │ │ │ │ -        {ping, Ping_PID} ->
    │ │ │ │ -            io:format("Pong received ping~n", []),
    │ │ │ │ +            io:format("Pong finished~n", []);
    │ │ │ │ +        {ping, Ping_PID} ->
    │ │ │ │ +            io:format("Pong received ping~n", []),
    │ │ │ │              Ping_PID ! pong,
    │ │ │ │ -            pong()
    │ │ │ │ +            pong()
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -start() ->
    │ │ │ │ -    register(pong, spawn(tut16, pong, [])),
    │ │ │ │ -    spawn(tut16, ping, [3]).
    2> c(tut16).
    │ │ │ │ -{ok, tut16}
    │ │ │ │ -3> tut16:start().
    │ │ │ │ +start() ->
    │ │ │ │ +    register(pong, spawn(tut16, pong, [])),
    │ │ │ │ +    spawn(tut16, ping, [3]).
    2> c(tut16).
    │ │ │ │ +{ok, tut16}
    │ │ │ │ +3> tut16:start().
    │ │ │ │  <0.38.0>
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  ping finished
    │ │ │ │ -Pong finished

    Here the start/0 function,

    register(pong, spawn(tut16, pong, [])),

    both spawns the "pong" process and gives it the name pong. In the "ping" │ │ │ │ -process, messages can be sent to pong by:

    pong ! {ping, self()},

    ping/2 now becomes ping/1 as the argument Pong_PID is not needed.

    │ │ │ │ +Pong finished

    Here the start/0 function,

    register(pong, spawn(tut16, pong, [])),

    both spawns the "pong" process and gives it the name pong. In the "ping" │ │ │ │ +process, messages can be sent to pong by:

    pong ! {ping, self()},

    ping/2 now becomes ping/1 as the argument Pong_PID is not needed.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Distributed Programming │ │ │ │

    │ │ │ │

    Let us rewrite the ping pong program with "ping" and "pong" on different │ │ │ │ computers. First a few things are needed to set up to get this to work. The │ │ │ │ @@ -255,106 +255,106 @@ │ │ │ │ of the file. This is a requirement.

    When you start an Erlang system that is going to talk to other Erlang systems, │ │ │ │ you must give it a name, for example:

    $ erl -sname my_name

    We will see more details of this later. If you want to experiment with │ │ │ │ distributed Erlang, but you only have one computer to work on, you can start two │ │ │ │ separate Erlang systems on the same computer but give them different names. Each │ │ │ │ Erlang system running on a computer is called an Erlang node.

    (Note: erl -sname assumes that all nodes are in the same IP domain and we can │ │ │ │ use only the first component of the IP address, if we want to use nodes in │ │ │ │ different domains we use -name instead, but then all IP address must be given │ │ │ │ -in full.)

    Here is the ping pong example modified to run on two separate nodes:

    -module(tut17).
    │ │ │ │ +in full.)

    Here is the ping pong example modified to run on two separate nodes:

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

    Let us assume there are two computers called gollum and kosken. First a node is │ │ │ │ +start_ping(Pong_Node) -> │ │ │ │ + spawn(tut17, ping, [3, Pong_Node]).

    Let us assume there are two computers called gollum and kosken. First a node is │ │ │ │ started on kosken, called ping, and then a node on gollum, called pong.

    On kosken (on a Linux/UNIX system):

    kosken> erl -sname ping
    │ │ │ │  Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0]
    │ │ │ │  
    │ │ │ │  Eshell V5.2.3.7  (abort with ^G)
    │ │ │ │  (ping@kosken)1>

    On gollum:

    gollum> erl -sname pong
    │ │ │ │  Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0]
    │ │ │ │  
    │ │ │ │  Eshell V5.2.3.7  (abort with ^G)
    │ │ │ │ -(pong@gollum)1>

    Now the "pong" process on gollum is started:

    (pong@gollum)1> tut17:start_pong().
    │ │ │ │ +(pong@gollum)1>

    Now the "pong" process on gollum is started:

    (pong@gollum)1> tut17:start_pong().
    │ │ │ │  true

    And the "ping" process on kosken is started (from the code above you can see │ │ │ │ that a parameter of the start_ping function is the node name of the Erlang │ │ │ │ -system where "pong" is running):

    (ping@kosken)1> tut17:start_ping(pong@gollum).
    │ │ │ │ +system where "pong" is running):

    (ping@kosken)1> tut17:start_ping(pong@gollum).
    │ │ │ │  <0.37.0>
    │ │ │ │  Ping received pong
    │ │ │ │  Ping received pong
    │ │ │ │  Ping received pong
    │ │ │ │  ping finished

    As shown, the ping pong program has run. On the "pong" side:

    (pong@gollum)2> 
    │ │ │ │  Pong received ping
    │ │ │ │  Pong received ping
    │ │ │ │  Pong received ping
    │ │ │ │  Pong finished
    │ │ │ │ -(pong@gollum)2> 

    Looking at the tut17 code, you see that the pong function itself is │ │ │ │ +(pong@gollum)2>

    Looking at the tut17 code, you see that the pong function itself is │ │ │ │ unchanged, the following lines work in the same way irrespective of on which │ │ │ │ -node the "ping" process is executes:

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

    {ping, Ping_PID} ->
    │ │ │ │ +    io:format("Pong received ping~n", []),
    │ │ │ │      Ping_PID ! pong,

    Thus, Erlang pids contain information about where the process executes. So if │ │ │ │ you know the pid of a process, the ! operator can be used to send it a │ │ │ │ -message disregarding if the process is on the same node or on a different node.

    A difference is how messages are sent to a registered process on another node:

    {pong, Pong_Node} ! {ping, self()},

    A tuple {registered_name,node_name} is used instead of just the │ │ │ │ +message disregarding if the process is on the same node or on a different node.

    A difference is how messages are sent to a registered process on another node:

    {pong, Pong_Node} ! {ping, self()},

    A tuple {registered_name,node_name} is used instead of just the │ │ │ │ registered_name.

    In the previous example, "ping" and "pong" were started from the shells of two │ │ │ │ separate Erlang nodes. spawn can also be used to start processes in other │ │ │ │ nodes.

    The next example is the ping pong program, yet again, but this time "ping" is │ │ │ │ -started in another node:

    -module(tut18).
    │ │ │ │ +started in another node:

    -module(tut18).
    │ │ │ │  
    │ │ │ │ --export([start/1,  ping/2, pong/0]).
    │ │ │ │ +-export([start/1,  ping/2, pong/0]).
    │ │ │ │  
    │ │ │ │ -ping(0, Pong_Node) ->
    │ │ │ │ -    {pong, Pong_Node} ! finished,
    │ │ │ │ -    io:format("ping finished~n", []);
    │ │ │ │ +ping(0, Pong_Node) ->
    │ │ │ │ +    {pong, Pong_Node} ! finished,
    │ │ │ │ +    io:format("ping finished~n", []);
    │ │ │ │  
    │ │ │ │ -ping(N, Pong_Node) ->
    │ │ │ │ -    {pong, Pong_Node} ! {ping, self()},
    │ │ │ │ +ping(N, Pong_Node) ->
    │ │ │ │ +    {pong, Pong_Node} ! {ping, self()},
    │ │ │ │      receive
    │ │ │ │          pong ->
    │ │ │ │ -            io:format("Ping received pong~n", [])
    │ │ │ │ +            io:format("Ping received pong~n", [])
    │ │ │ │      end,
    │ │ │ │ -    ping(N - 1, Pong_Node).
    │ │ │ │ +    ping(N - 1, Pong_Node).
    │ │ │ │  
    │ │ │ │ -pong() ->
    │ │ │ │ +pong() ->
    │ │ │ │      receive
    │ │ │ │          finished ->
    │ │ │ │ -            io:format("Pong finished~n", []);
    │ │ │ │ -        {ping, Ping_PID} ->
    │ │ │ │ -            io:format("Pong received ping~n", []),
    │ │ │ │ +            io:format("Pong finished~n", []);
    │ │ │ │ +        {ping, Ping_PID} ->
    │ │ │ │ +            io:format("Pong received ping~n", []),
    │ │ │ │              Ping_PID ! pong,
    │ │ │ │ -            pong()
    │ │ │ │ +            pong()
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -start(Ping_Node) ->
    │ │ │ │ -    register(pong, spawn(tut18, pong, [])),
    │ │ │ │ -    spawn(Ping_Node, tut18, ping, [3, node()]).

    Assuming an Erlang system called ping (but not the "ping" process) has already │ │ │ │ -been started on kosken, then on gollum this is done:

    (pong@gollum)1> tut18:start(ping@kosken).
    │ │ │ │ +start(Ping_Node) ->
    │ │ │ │ +    register(pong, spawn(tut18, pong, [])),
    │ │ │ │ +    spawn(Ping_Node, tut18, ping, [3, node()]).

    Assuming an Erlang system called ping (but not the "ping" process) has already │ │ │ │ +been started on kosken, then on gollum this is done:

    (pong@gollum)1> tut18:start(ping@kosken).
    │ │ │ │  <3934.39.0>
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │  Pong received ping
    │ │ │ │  Ping received pong
    │ │ │ │ @@ -421,184 +421,184 @@
    │ │ │ │  %%% Started: messenger:client(Server_Node, Name)
    │ │ │ │  %%% To client: logoff
    │ │ │ │  %%% To client: {message_to, ToName, Message}
    │ │ │ │  %%%
    │ │ │ │  %%% Configuration: change the server_node() function to return the
    │ │ │ │  %%% name of the node where the messenger server runs
    │ │ │ │  
    │ │ │ │ --module(messenger).
    │ │ │ │ --export([start_server/0, server/1, logon/1, logoff/0, message/2, client/2]).
    │ │ │ │ +-module(messenger).
    │ │ │ │ +-export([start_server/0, server/1, logon/1, logoff/0, message/2, client/2]).
    │ │ │ │  
    │ │ │ │  %%% Change the function below to return the name of the node where the
    │ │ │ │  %%% messenger server runs
    │ │ │ │ -server_node() ->
    │ │ │ │ +server_node() ->
    │ │ │ │      messenger@super.
    │ │ │ │  
    │ │ │ │  %%% This is the server process for the "messenger"
    │ │ │ │  %%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
    │ │ │ │ -server(User_List) ->
    │ │ │ │ +server(User_List) ->
    │ │ │ │      receive
    │ │ │ │ -        {From, logon, Name} ->
    │ │ │ │ -            New_User_List = server_logon(From, Name, User_List),
    │ │ │ │ -            server(New_User_List);
    │ │ │ │ -        {From, logoff} ->
    │ │ │ │ -            New_User_List = server_logoff(From, User_List),
    │ │ │ │ -            server(New_User_List);
    │ │ │ │ -        {From, message_to, To, Message} ->
    │ │ │ │ -            server_transfer(From, To, Message, User_List),
    │ │ │ │ -            io:format("list is now: ~p~n", [User_List]),
    │ │ │ │ -            server(User_List)
    │ │ │ │ +        {From, logon, Name} ->
    │ │ │ │ +            New_User_List = server_logon(From, Name, User_List),
    │ │ │ │ +            server(New_User_List);
    │ │ │ │ +        {From, logoff} ->
    │ │ │ │ +            New_User_List = server_logoff(From, User_List),
    │ │ │ │ +            server(New_User_List);
    │ │ │ │ +        {From, message_to, To, Message} ->
    │ │ │ │ +            server_transfer(From, To, Message, User_List),
    │ │ │ │ +            io:format("list is now: ~p~n", [User_List]),
    │ │ │ │ +            server(User_List)
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │  %%% Start the server
    │ │ │ │ -start_server() ->
    │ │ │ │ -    register(messenger, spawn(messenger, server, [[]])).
    │ │ │ │ +start_server() ->
    │ │ │ │ +    register(messenger, spawn(messenger, server, [[]])).
    │ │ │ │  
    │ │ │ │  
    │ │ │ │  %%% Server adds a new user to the user list
    │ │ │ │ -server_logon(From, Name, User_List) ->
    │ │ │ │ +server_logon(From, Name, User_List) ->
    │ │ │ │      %% check if logged on anywhere else
    │ │ │ │ -    case lists:keymember(Name, 2, User_List) of
    │ │ │ │ +    case lists:keymember(Name, 2, User_List) of
    │ │ │ │          true ->
    │ │ │ │ -            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
    │ │ │ │ +            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
    │ │ │ │              User_List;
    │ │ │ │          false ->
    │ │ │ │ -            From ! {messenger, logged_on},
    │ │ │ │ -            [{From, Name} | User_List]        %add user to the list
    │ │ │ │ +            From ! {messenger, logged_on},
    │ │ │ │ +            [{From, Name} | User_List]        %add user to the list
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │  %%% Server deletes a user from the user list
    │ │ │ │ -server_logoff(From, User_List) ->
    │ │ │ │ -    lists:keydelete(From, 1, User_List).
    │ │ │ │ +server_logoff(From, User_List) ->
    │ │ │ │ +    lists:keydelete(From, 1, User_List).
    │ │ │ │  
    │ │ │ │  
    │ │ │ │  %%% Server transfers a message between user
    │ │ │ │ -server_transfer(From, To, Message, User_List) ->
    │ │ │ │ +server_transfer(From, To, Message, User_List) ->
    │ │ │ │      %% check that the user is logged on and who he is
    │ │ │ │ -    case lists:keysearch(From, 1, User_List) of
    │ │ │ │ +    case lists:keysearch(From, 1, User_List) of
    │ │ │ │          false ->
    │ │ │ │ -            From ! {messenger, stop, you_are_not_logged_on};
    │ │ │ │ -        {value, {From, Name}} ->
    │ │ │ │ -            server_transfer(From, Name, To, Message, User_List)
    │ │ │ │ +            From ! {messenger, stop, you_are_not_logged_on};
    │ │ │ │ +        {value, {From, Name}} ->
    │ │ │ │ +            server_transfer(From, Name, To, Message, User_List)
    │ │ │ │      end.
    │ │ │ │  %%% If the user exists, send the message
    │ │ │ │ -server_transfer(From, Name, To, Message, User_List) ->
    │ │ │ │ +server_transfer(From, Name, To, Message, User_List) ->
    │ │ │ │      %% Find the receiver and send the message
    │ │ │ │ -    case lists:keysearch(To, 2, User_List) of
    │ │ │ │ +    case lists:keysearch(To, 2, User_List) of
    │ │ │ │          false ->
    │ │ │ │ -            From ! {messenger, receiver_not_found};
    │ │ │ │ -        {value, {ToPid, To}} ->
    │ │ │ │ -            ToPid ! {message_from, Name, Message},
    │ │ │ │ -            From ! {messenger, sent}
    │ │ │ │ +            From ! {messenger, receiver_not_found};
    │ │ │ │ +        {value, {ToPid, To}} ->
    │ │ │ │ +            ToPid ! {message_from, Name, Message},
    │ │ │ │ +            From ! {messenger, sent}
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │  
    │ │ │ │  %%% User Commands
    │ │ │ │ -logon(Name) ->
    │ │ │ │ -    case whereis(mess_client) of
    │ │ │ │ +logon(Name) ->
    │ │ │ │ +    case whereis(mess_client) of
    │ │ │ │          undefined ->
    │ │ │ │ -            register(mess_client,
    │ │ │ │ -                     spawn(messenger, client, [server_node(), Name]));
    │ │ │ │ +            register(mess_client,
    │ │ │ │ +                     spawn(messenger, client, [server_node(), Name]));
    │ │ │ │          _ -> already_logged_on
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -logoff() ->
    │ │ │ │ +logoff() ->
    │ │ │ │      mess_client ! logoff.
    │ │ │ │  
    │ │ │ │ -message(ToName, Message) ->
    │ │ │ │ -    case whereis(mess_client) of % Test if the client is running
    │ │ │ │ +message(ToName, Message) ->
    │ │ │ │ +    case whereis(mess_client) of % Test if the client is running
    │ │ │ │          undefined ->
    │ │ │ │              not_logged_on;
    │ │ │ │ -        _ -> mess_client ! {message_to, ToName, Message},
    │ │ │ │ +        _ -> mess_client ! {message_to, ToName, Message},
    │ │ │ │               ok
    │ │ │ │  end.
    │ │ │ │  
    │ │ │ │  
    │ │ │ │  %%% The client process which runs on each server node
    │ │ │ │ -client(Server_Node, Name) ->
    │ │ │ │ -    {messenger, Server_Node} ! {self(), logon, Name},
    │ │ │ │ -    await_result(),
    │ │ │ │ -    client(Server_Node).
    │ │ │ │ +client(Server_Node, Name) ->
    │ │ │ │ +    {messenger, Server_Node} ! {self(), logon, Name},
    │ │ │ │ +    await_result(),
    │ │ │ │ +    client(Server_Node).
    │ │ │ │  
    │ │ │ │ -client(Server_Node) ->
    │ │ │ │ +client(Server_Node) ->
    │ │ │ │      receive
    │ │ │ │          logoff ->
    │ │ │ │ -            {messenger, Server_Node} ! {self(), logoff},
    │ │ │ │ -            exit(normal);
    │ │ │ │ -        {message_to, ToName, Message} ->
    │ │ │ │ -            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
    │ │ │ │ -            await_result();
    │ │ │ │ -        {message_from, FromName, Message} ->
    │ │ │ │ -            io:format("Message from ~p: ~p~n", [FromName, Message])
    │ │ │ │ +            {messenger, Server_Node} ! {self(), logoff},
    │ │ │ │ +            exit(normal);
    │ │ │ │ +        {message_to, ToName, Message} ->
    │ │ │ │ +            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
    │ │ │ │ +            await_result();
    │ │ │ │ +        {message_from, FromName, Message} ->
    │ │ │ │ +            io:format("Message from ~p: ~p~n", [FromName, Message])
    │ │ │ │      end,
    │ │ │ │ -    client(Server_Node).
    │ │ │ │ +    client(Server_Node).
    │ │ │ │  
    │ │ │ │  %%% wait for a response from the server
    │ │ │ │ -await_result() ->
    │ │ │ │ +await_result() ->
    │ │ │ │      receive
    │ │ │ │ -        {messenger, stop, Why} -> % Stop the client
    │ │ │ │ -            io:format("~p~n", [Why]),
    │ │ │ │ -            exit(normal);
    │ │ │ │ -        {messenger, What} ->  % Normal response
    │ │ │ │ -            io:format("~p~n", [What])
    │ │ │ │ +        {messenger, stop, Why} -> % Stop the client
    │ │ │ │ +            io:format("~p~n", [Why]),
    │ │ │ │ +            exit(normal);
    │ │ │ │ +        {messenger, What} ->  % Normal response
    │ │ │ │ +            io:format("~p~n", [What])
    │ │ │ │      end.

    To use this program, you need to:

    • Configure the server_node() function.
    • Copy the compiled code (messenger.beam) to the directory on each computer │ │ │ │ where you start Erlang.

    In the following example using this program, nodes are started on four different │ │ │ │ computers. If you do not have that many machines available on your network, you │ │ │ │ can start several nodes on the same machine.

    Four Erlang nodes are started up: messenger@super, c1@bilbo, c2@kosken, │ │ │ │ -c3@gollum.

    First the server at messenger@super is started up:

    (messenger@super)1> messenger:start_server().
    │ │ │ │ -true

    Now Peter logs on at c1@bilbo:

    (c1@bilbo)1> messenger:logon(peter).
    │ │ │ │ +c3@gollum.

    First the server at messenger@super is started up:

    (messenger@super)1> messenger:start_server().
    │ │ │ │ +true

    Now Peter logs on at c1@bilbo:

    (c1@bilbo)1> messenger:logon(peter).
    │ │ │ │  true
    │ │ │ │ -logged_on

    James logs on at c2@kosken:

    (c2@kosken)1> messenger:logon(james).
    │ │ │ │ +logged_on

    James logs on at c2@kosken:

    (c2@kosken)1> messenger:logon(james).
    │ │ │ │  true
    │ │ │ │ -logged_on

    And Fred logs on at c3@gollum:

    (c3@gollum)1> messenger:logon(fred).
    │ │ │ │ +logged_on

    And Fred logs on at c3@gollum:

    (c3@gollum)1> messenger:logon(fred).
    │ │ │ │  true
    │ │ │ │ -logged_on

    Now Peter sends Fred a message:

    (c1@bilbo)2> messenger:message(fred, "hello").
    │ │ │ │ +logged_on

    Now Peter sends Fred a message:

    (c1@bilbo)2> messenger:message(fred, "hello").
    │ │ │ │  ok
    │ │ │ │  sent

    Fred receives the message and sends a message to Peter and logs off:

    Message from peter: "hello"
    │ │ │ │ -(c3@gollum)2> messenger:message(peter, "go away, I'm busy").
    │ │ │ │ +(c3@gollum)2> messenger:message(peter, "go away, I'm busy").
    │ │ │ │  ok
    │ │ │ │  sent
    │ │ │ │ -(c3@gollum)3> messenger:logoff().
    │ │ │ │ -logoff

    James now tries to send a message to Fred:

    (c2@kosken)2> messenger:message(fred, "peter doesn't like you").
    │ │ │ │ +(c3@gollum)3> messenger:logoff().
    │ │ │ │ +logoff

    James now tries to send a message to Fred:

    (c2@kosken)2> messenger:message(fred, "peter doesn't like you").
    │ │ │ │  ok
    │ │ │ │  receiver_not_found

    But this fails as Fred has already logged off.

    First let us look at some of the new concepts that have been introduced.

    There are two versions of the server_transfer function: one with four │ │ │ │ arguments (server_transfer/4) and one with five (server_transfer/5). These │ │ │ │ are regarded by Erlang as two separate functions.

    Notice how to write the server function so that it calls itself, through │ │ │ │ server(User_List), and thus creates a loop. The Erlang compiler is "clever" │ │ │ │ and optimizes the code so that this really is a sort of loop and not a proper │ │ │ │ function call. But this only works if there is no code after the call. │ │ │ │ Otherwise, the compiler expects the call to return and make a proper function │ │ │ │ call. This would result in the process getting bigger and bigger for every loop.

    Functions in the lists module are used. This is a very useful module and a │ │ │ │ study of the manual page is recommended (erl -man lists). │ │ │ │ lists:keymember(Key,Position,Lists) looks through a list of tuples and looks │ │ │ │ at Position in each tuple to see if it is the same as Key. The first element │ │ │ │ is position 1. If it finds a tuple where the element at Position is the same │ │ │ │ -as Key, it returns true, otherwise false.

    3> lists:keymember(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
    │ │ │ │ +as Key, it returns true, otherwise false.

    3> lists:keymember(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
    │ │ │ │  true
    │ │ │ │ -4> lists:keymember(p, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
    │ │ │ │ +4> lists:keymember(p, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
    │ │ │ │  false

    lists:keydelete works in the same way but deletes the first tuple found (if │ │ │ │ -any) and returns the remaining list:

    5> lists:keydelete(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
    │ │ │ │ -[{x,y,z},{b,b,b},{q,r,s}]

    lists:keysearch is like lists:keymember, but it returns │ │ │ │ +any) and returns the remaining list:

    5> lists:keydelete(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
    │ │ │ │ +[{x,y,z},{b,b,b},{q,r,s}]

    lists:keysearch is like lists:keymember, but it returns │ │ │ │ {value,Tuple_Found} or the atom false.

    There are many very useful functions in the lists module.

    An Erlang process (conceptually) runs until it does a receive and there is no │ │ │ │ message which it wants to receive in the message queue. "conceptually" is used │ │ │ │ here because the Erlang system shares the CPU time between the active processes │ │ │ │ in the system.

    A process terminates when there is nothing more for it to do, that is, the last │ │ │ │ function it calls simply returns and does not call another function. Another way │ │ │ │ for a process to terminate is for it to call exit/1. The argument │ │ │ │ to exit/1 has a special meaning, which is discussed later. In this │ │ │ │ example, exit(normal) is done, which has the same effect as a │ │ │ │ process running out of functions to call.

    The BIF whereis(RegisteredName) checks if a registered process │ │ │ │ of name RegisteredName exists. If it exists, the pid of that process is │ │ │ │ returned. If it does not exist, the atom undefined is returned.

    You should by now be able to understand most of the code in the │ │ │ │ messenger-module. Let us study one case in detail: a message is sent from one │ │ │ │ -user to another.

    The first user "sends" the message in the example above by:

    messenger:message(fred, "hello")

    After testing that the client process exists:

    whereis(mess_client)

    And a message is sent to mess_client:

    mess_client ! {message_to, fred, "hello"}

    The client sends the message to the server by:

    {messenger, messenger@super} ! {self(), message_to, fred, "hello"},

    And waits for a reply from the server.

    The server receives this message and calls:

    server_transfer(From, fred, "hello", User_List),

    This checks that the pid From is in the User_List:

    lists:keysearch(From, 1, User_List)

    If keysearch returns the atom false, some error has occurred and the server │ │ │ │ -sends back the message:

    From ! {messenger, stop, you_are_not_logged_on}

    This is received by the client, which in turn does exit(normal) │ │ │ │ +user to another.

    The first user "sends" the message in the example above by:

    messenger:message(fred, "hello")

    After testing that the client process exists:

    whereis(mess_client)

    And a message is sent to mess_client:

    mess_client ! {message_to, fred, "hello"}

    The client sends the message to the server by:

    {messenger, messenger@super} ! {self(), message_to, fred, "hello"},

    And waits for a reply from the server.

    The server receives this message and calls:

    server_transfer(From, fred, "hello", User_List),

    This checks that the pid From is in the User_List:

    lists:keysearch(From, 1, User_List)

    If keysearch returns the atom false, some error has occurred and the server │ │ │ │ +sends back the message:

    From ! {messenger, stop, you_are_not_logged_on}

    This is received by the client, which in turn does exit(normal) │ │ │ │ and terminates. If keysearch returns {value,{From,Name}} it is certain that │ │ │ │ -the user is logged on and that his name (peter) is in variable Name.

    Let us now call:

    server_transfer(From, peter, fred, "hello", User_List)

    Notice that as this is server_transfer/5, it is not the same as the previous │ │ │ │ +the user is logged on and that his name (peter) is in variable Name.

    Let us now call:

    server_transfer(From, peter, fred, "hello", User_List)

    Notice that as this is server_transfer/5, it is not the same as the previous │ │ │ │ function server_transfer/4. Another keysearch is done on User_List to find │ │ │ │ -the pid of the client corresponding to fred:

    lists:keysearch(fred, 2, User_List)

    This time argument 2 is used, which is the second element in the tuple. If this │ │ │ │ +the pid of the client corresponding to fred:

    lists:keysearch(fred, 2, User_List)

    This time argument 2 is used, which is the second element in the tuple. If this │ │ │ │ returns the atom false, fred is not logged on and the following message is │ │ │ │ -sent:

    From ! {messenger, receiver_not_found};

    This is received by the client.

    If keysearch returns:

    {value, {ToPid, fred}}

    The following message is sent to fred's client:

    ToPid ! {message_from, peter, "hello"},

    The following message is sent to peter's client:

    From ! {messenger, sent}

    Fred's client receives the message and prints it:

    {message_from, peter, "hello"} ->
    │ │ │ │ -    io:format("Message from ~p: ~p~n", [peter, "hello"])

    Peter's client receives the message in the await_result function.

    │ │ │ │ +sent:

    From ! {messenger, receiver_not_found};

    This is received by the client.

    If keysearch returns:

    {value, {ToPid, fred}}

    The following message is sent to fred's client:

    ToPid ! {message_from, peter, "hello"},

    The following message is sent to peter's client:

    From ! {messenger, sent}

    Fred's client receives the message and prints it:

    {message_from, peter, "hello"} ->
    │ │ │ │ +    io:format("Message from ~p: ~p~n", [peter, "hello"])

    Peter's client receives the message in the await_result function.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/commoncaveats.xhtml │ │ │ │ @@ -23,31 +23,31 @@ │ │ │ │

    This section lists a few constructs to watch out for.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Operator ++ │ │ │ │

    │ │ │ │

    The ++ operator copies its left-hand side operand. That is clearly │ │ │ │ -seen if we do our own implementation in Erlang:

    my_plus_plus([H|T], Tail) ->
    │ │ │ │ -    [H|my_plus_plus(T, Tail)];
    │ │ │ │ -my_plus_plus([], Tail) ->
    │ │ │ │ -    Tail.

    We must be careful how we use ++ in a loop. First is how not to use it:

    DO NOT

    naive_reverse([H|T]) ->
    │ │ │ │ -    naive_reverse(T) ++ [H];
    │ │ │ │ -naive_reverse([]) ->
    │ │ │ │ -    [].

    As the ++ operator copies its left-hand side operand, the growing │ │ │ │ -result is copied repeatedly, leading to quadratic complexity.

    On the other hand, using ++ in loop like this is perfectly fine:

    OK

    naive_but_ok_reverse(List) ->
    │ │ │ │ -    naive_but_ok_reverse(List, []).
    │ │ │ │ +seen if we do our own implementation in Erlang:

    my_plus_plus([H|T], Tail) ->
    │ │ │ │ +    [H|my_plus_plus(T, Tail)];
    │ │ │ │ +my_plus_plus([], Tail) ->
    │ │ │ │ +    Tail.

    We must be careful how we use ++ in a loop. First is how not to use it:

    DO NOT

    naive_reverse([H|T]) ->
    │ │ │ │ +    naive_reverse(T) ++ [H];
    │ │ │ │ +naive_reverse([]) ->
    │ │ │ │ +    [].

    As the ++ operator copies its left-hand side operand, the growing │ │ │ │ +result is copied repeatedly, leading to quadratic complexity.

    On the other hand, using ++ in loop like this is perfectly fine:

    OK

    naive_but_ok_reverse(List) ->
    │ │ │ │ +    naive_but_ok_reverse(List, []).
    │ │ │ │  
    │ │ │ │ -naive_but_ok_reverse([H|T], Acc) ->
    │ │ │ │ -    naive_but_ok_reverse(T, [H] ++ Acc);
    │ │ │ │ -naive_but_ok_reverse([], Acc) ->
    │ │ │ │ +naive_but_ok_reverse([H|T], Acc) ->
    │ │ │ │ +    naive_but_ok_reverse(T, [H] ++ Acc);
    │ │ │ │ +naive_but_ok_reverse([], Acc) ->
    │ │ │ │      Acc.

    Each list element is copied only once. The growing result Acc is the right-hand │ │ │ │ -side operand, which it is not copied.

    Experienced Erlang programmers would probably write as follows:

    DO

    vanilla_reverse([H|T], Acc) ->
    │ │ │ │ -    vanilla_reverse(T, [H|Acc]);
    │ │ │ │ -vanilla_reverse([], Acc) ->
    │ │ │ │ +side operand, which it is not copied.

    Experienced Erlang programmers would probably write as follows:

    DO

    vanilla_reverse([H|T], Acc) ->
    │ │ │ │ +    vanilla_reverse(T, [H|Acc]);
    │ │ │ │ +vanilla_reverse([], Acc) ->
    │ │ │ │      Acc.

    In principle, this is slightly more efficient because the list element [H] │ │ │ │ is not built before being copied and discarded. In practice, the compiler │ │ │ │ rewrites [H] ++ Acc to [H|Acc].

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Timer Module │ │ │ │ @@ -65,77 +65,77 @@ │ │ │ │ therefore harmless.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Accidental Copying and Loss of Sharing │ │ │ │

    │ │ │ │

    When spawning a new process using a fun, one can accidentally copy more data to │ │ │ │ -the process than intended. For example:

    DO NOT

    accidental1(State) ->
    │ │ │ │ -    spawn(fun() ->
    │ │ │ │ -                  io:format("~p\n", [State#state.info])
    │ │ │ │ -          end).

    The code in the fun will extract one element from the record and print it. The │ │ │ │ +the process than intended. For example:

    DO NOT

    accidental1(State) ->
    │ │ │ │ +    spawn(fun() ->
    │ │ │ │ +                  io:format("~p\n", [State#state.info])
    │ │ │ │ +          end).

    The code in the fun will extract one element from the record and print it. The │ │ │ │ rest of the state record is not used. However, when the spawn/1 │ │ │ │ -function is executed, the entire record is copied to the newly created process.

    The same kind of problem can happen with a map:

    DO NOT

    accidental2(State) ->
    │ │ │ │ -    spawn(fun() ->
    │ │ │ │ -                  io:format("~p\n", [map_get(info, State)])
    │ │ │ │ -          end).

    In the following example (part of a module implementing the gen_server │ │ │ │ -behavior) the created fun is sent to another process:

    DO NOT

    handle_call(give_me_a_fun, _From, State) ->
    │ │ │ │ -    Fun = fun() -> State#state.size =:= 42 end,
    │ │ │ │ -    {reply, Fun, State}.

    How bad that unnecessary copy is depends on the contents of the record or the │ │ │ │ -map.

    For example, if the state record is initialized like this:

    init1() ->
    │ │ │ │ -    #state{data=lists:seq(1, 10000)}.

    a list with 10000 elements (or about 20000 heap words) will be copied to the │ │ │ │ +function is executed, the entire record is copied to the newly created process.

    The same kind of problem can happen with a map:

    DO NOT

    accidental2(State) ->
    │ │ │ │ +    spawn(fun() ->
    │ │ │ │ +                  io:format("~p\n", [map_get(info, State)])
    │ │ │ │ +          end).

    In the following example (part of a module implementing the gen_server │ │ │ │ +behavior) the created fun is sent to another process:

    DO NOT

    handle_call(give_me_a_fun, _From, State) ->
    │ │ │ │ +    Fun = fun() -> State#state.size =:= 42 end,
    │ │ │ │ +    {reply, Fun, State}.

    How bad that unnecessary copy is depends on the contents of the record or the │ │ │ │ +map.

    For example, if the state record is initialized like this:

    init1() ->
    │ │ │ │ +    #state{data=lists:seq(1, 10000)}.

    a list with 10000 elements (or about 20000 heap words) will be copied to the │ │ │ │ newly created process.

    An unnecessary copy of 10000 element list can be bad enough, but it can get even │ │ │ │ worse if the state record contains shared subterms. Here is a simple example │ │ │ │ -of a term with a shared subterm:

    {SubTerm, SubTerm}

    When a term is copied to another process, sharing of subterms will be lost and │ │ │ │ -the copied term can be many times larger than the original term. For example:

    init2() ->
    │ │ │ │ -    SharedSubTerms = lists:foldl(fun(_, A) -> [A|A] end, [0], lists:seq(1, 15)),
    │ │ │ │ -    #state{data=Shared}.

    In the process that calls init2/0, the size of the data field in the state │ │ │ │ +of a term with a shared subterm:

    {SubTerm, SubTerm}

    When a term is copied to another process, sharing of subterms will be lost and │ │ │ │ +the copied term can be many times larger than the original term. For example:

    init2() ->
    │ │ │ │ +    SharedSubTerms = lists:foldl(fun(_, A) -> [A|A] end, [0], lists:seq(1, 15)),
    │ │ │ │ +    #state{data=Shared}.

    In the process that calls init2/0, the size of the data field in the state │ │ │ │ record will be 32 heap words. When the record is copied to the newly created │ │ │ │ process, sharing will be lost and the size of the copied data field will be │ │ │ │ 131070 heap words. More details about │ │ │ │ loss off sharing are found in a later │ │ │ │ section.

    To avoid the problem, outside of the fun extract only the fields of the record │ │ │ │ -that are actually used:

    DO

    fixed_accidental1(State) ->
    │ │ │ │ +that are actually used:

    DO

    fixed_accidental1(State) ->
    │ │ │ │      Info = State#state.info,
    │ │ │ │ -    spawn(fun() ->
    │ │ │ │ -                  io:format("~p\n", [Info])
    │ │ │ │ -          end).

    Similarly, outside of the fun extract only the map elements that are actually │ │ │ │ -used:

    DO

    fixed_accidental2(State) ->
    │ │ │ │ -    Info = map_get(info, State),
    │ │ │ │ -    spawn(fun() ->
    │ │ │ │ -                  io:format("~p\n", [Info])
    │ │ │ │ -          end).

    │ │ │ │ + spawn(fun() -> │ │ │ │ + io:format("~p\n", [Info]) │ │ │ │ + end).

    Similarly, outside of the fun extract only the map elements that are actually │ │ │ │ +used:

    DO

    fixed_accidental2(State) ->
    │ │ │ │ +    Info = map_get(info, State),
    │ │ │ │ +    spawn(fun() ->
    │ │ │ │ +                  io:format("~p\n", [Info])
    │ │ │ │ +          end).

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ list_to_atom/1 │ │ │ │

    │ │ │ │

    Atoms are not garbage-collected. Once an atom is created, it is never removed. │ │ │ │ The emulator terminates if the limit for the number of atoms (1,048,576 by │ │ │ │ default) is reached.

    Therefore, converting arbitrary input strings to atoms can be dangerous in a │ │ │ │ system that runs continuously. If only certain well-defined atoms are allowed as │ │ │ │ input, list_to_existing_atom/1 or │ │ │ │ binary_to_existing_atom/1 can be used │ │ │ │ to guard against a denial-of-service attack. (All atoms that are allowed must │ │ │ │ have been created earlier, for example, by using all of them in a module │ │ │ │ and loading that module.)

    Using list_to_atom/1 to construct an atom that │ │ │ │ -is passed to apply/3 is quite expensive.

    DO NOT

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

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

    DO NOT

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

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ length/1 │ │ │ │

    │ │ │ │

    The time for calculating the length of a list is proportional to the length of │ │ │ │ the list, as opposed to tuple_size/1, │ │ │ │ byte_size/1, and bit_size/1, which all │ │ │ │ execute in constant time.

    Normally, there is no need to worry about the speed of length/1, │ │ │ │ because it is efficiently implemented in C. In time-critical code, you might │ │ │ │ want to avoid it if the input list could potentially be very long.

    Some uses of length/1 can be replaced by matching. For example, │ │ │ │ -the following code:

    foo(L) when length(L) >= 3 ->
    │ │ │ │ -    ...

    can be rewritten to:

    foo([_,_,_|_]=L) ->
    │ │ │ │ +the following code:

    foo(L) when length(L) >= 3 ->
    │ │ │ │ +    ...

    can be rewritten to:

    foo([_,_,_|_]=L) ->
    │ │ │ │     ...

    One slight difference is that length(L) fails if L is an │ │ │ │ improper list, while the pattern in the second code fragment accepts an improper │ │ │ │ list.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ setelement/3 │ │ │ │ @@ -143,18 +143,18 @@ │ │ │ │

    setelement/3 copies the tuple it modifies. Therefore, │ │ │ │ updating a tuple in a loop using setelement/3 creates a new │ │ │ │ copy of the tuple every time.

    There is one exception to the rule that the tuple is copied. If the compiler │ │ │ │ clearly can see that destructively updating the tuple would give the same result │ │ │ │ as if the tuple was copied, the call to setelement/3 is │ │ │ │ replaced with a special destructive setelement instruction. In the following │ │ │ │ code sequence, the first setelement/3 call copies the tuple │ │ │ │ -and modifies the ninth element:

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

    The two following setelement/3 calls modify the tuple in │ │ │ │ +and modifies the ninth element:

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

    The two following setelement/3 calls modify the tuple in │ │ │ │ place.

    For the optimization to be applied, all the following conditions must be true:

    • The tuple argument must be known to be a tuple of a known size.
    • The indices must be integer literals, not variables or expressions.
    • The indices must be given in descending order.
    • There must be no calls to another function in between the calls to │ │ │ │ setelement/3.
    • The tuple returned from one setelement/3 call must only be │ │ │ │ used in the subsequent call to setelement/3.

    If the code cannot be structured as in the multiple_setelement/1 example, the │ │ │ │ best way to modify multiple elements in a large tuple is to convert the tuple to │ │ │ │ a list, modify the list, and convert it back to a tuple.

    │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/code_loading.xhtml │ │ │ │ @@ -27,16 +27,16 @@ │ │ │ │ │ │ │ │ │ │ │ │ Compilation │ │ │ │

    │ │ │ │

    Erlang programs must be compiled to object code. The compiler can generate a │ │ │ │ new file that contains the object code. The current abstract machine, which runs │ │ │ │ the object code, is called BEAM, therefore the object files get the suffix │ │ │ │ -.beam. The compiler can also generate a binary which can be loaded directly.

    The compiler is located in the module compile in Compiler.

    compile:file(Module)
    │ │ │ │ -compile:file(Module, Options)

    The Erlang shell understands the command c(Module), which both compiles and │ │ │ │ +.beam. The compiler can also generate a binary which can be loaded directly.

    The compiler is located in the module compile in Compiler.

    compile:file(Module)
    │ │ │ │ +compile:file(Module, Options)

    The Erlang shell understands the command c(Module), which both compiles and │ │ │ │ loads Module.

    There is also a module make, which provides a set of functions similar to the │ │ │ │ UNIX type Make functions, see module make in Tools.

    The compiler can also be accessed from the OS prompt using the │ │ │ │ erl executable in ERTS.

    % erl -compile Module1...ModuleN
    │ │ │ │  % erl -make

    The erlc program provides way to compile modules from the OS │ │ │ │ shell, see the erlc executable in ERTS. It │ │ │ │ understands a number of flags that can be used to define macros, add search │ │ │ │ paths for include files, and more.

    % erlc <flags> File1.erl...FileN.erl

    │ │ │ │ @@ -61,51 +61,51 @@ │ │ │ │ When a module is loaded into the system for the first time, the code becomes │ │ │ │ 'current'. If then a new instance of the module is loaded, the code of the │ │ │ │ previous instance becomes 'old' and the new instance becomes 'current'.

    Both old and current code is valid, and can be evaluated concurrently. Fully │ │ │ │ qualified function calls always refer to current code. Old code can still be │ │ │ │ evaluated because of processes lingering in the old code.

    If a third instance of the module is loaded, the code server removes (purges) │ │ │ │ the old code and any processes lingering in it is terminated. Then the third │ │ │ │ instance becomes 'current' and the previously current code becomes 'old'.

    To change from old code to current code, a process must make a fully qualified │ │ │ │ -function call.

    Example:

    -module(m).
    │ │ │ │ --export([loop/0]).
    │ │ │ │ +function call.

    Example:

    -module(m).
    │ │ │ │ +-export([loop/0]).
    │ │ │ │  
    │ │ │ │ -loop() ->
    │ │ │ │ +loop() ->
    │ │ │ │      receive
    │ │ │ │          code_switch ->
    │ │ │ │ -            m:loop();
    │ │ │ │ +            m:loop();
    │ │ │ │          Msg ->
    │ │ │ │              ...
    │ │ │ │ -            loop()
    │ │ │ │ +            loop()
    │ │ │ │      end.

    To make the process change code, send the message code_switch to it. The │ │ │ │ process then makes a fully qualified call to m:loop() and changes to current │ │ │ │ code. Notice that m:loop/0 must be exported.

    For code replacement of funs to work, use the syntax │ │ │ │ fun Module:FunctionName/Arity.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Running a Function When a Module is Loaded │ │ │ │

    │ │ │ │

    The -on_load() directive names a function that is to be run automatically when │ │ │ │ -a module is loaded.

    Its syntax is as follows:

    -on_load(Name/0).

    It is not necessary to export the function. It is called in a freshly spawned │ │ │ │ +a module is loaded.

    Its syntax is as follows:

    -on_load(Name/0).

    It is not necessary to export the function. It is called in a freshly spawned │ │ │ │ process (which terminates as soon as the function returns).

    The function must return ok if the module is to become the new current code │ │ │ │ for the module and become callable.

    Returning any other value or generating an exception causes the new code to be │ │ │ │ unloaded. If the return value is not an atom, a warning error report is sent to │ │ │ │ the error logger.

    If there already is current code for the module, that code will remain current │ │ │ │ and can be called until the on_load function has returned. If the on_load │ │ │ │ function fails, the current code (if any) will remain current. If there is no │ │ │ │ current code for a module, any process that makes an external call to the module │ │ │ │ before the on_load function has finished will be suspended until the on_load │ │ │ │ function have finished.

    Change

    Before Erlang/OTP 19, if the on_load function failed, any previously current │ │ │ │ code would become old, essentially leaving the system without any working and │ │ │ │ reachable instance of the module.

    In embedded mode, first all modules are loaded. Then all on_load functions are │ │ │ │ called. The system is terminated unless all of the on_load functions return │ │ │ │ -ok.

    Example:

    -module(m).
    │ │ │ │ --on_load(load_my_nifs/0).
    │ │ │ │ +ok.

    Example:

    -module(m).
    │ │ │ │ +-on_load(load_my_nifs/0).
    │ │ │ │  
    │ │ │ │ -load_my_nifs() ->
    │ │ │ │ +load_my_nifs() ->
    │ │ │ │      NifPath = ...,    %Set up the path to the NIF library.
    │ │ │ │      Info = ...,       %Initialize the Info term
    │ │ │ │ -    erlang:load_nif(NifPath, Info).

    If the call to erlang:load_nif/2 fails, the module is unloaded and a warning │ │ │ │ + erlang:load_nif(NifPath, Info).

    If the call to erlang:load_nif/2 fails, the module is unloaded and a warning │ │ │ │ report is sent to the error loader.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ ├── OEBPS/c_portdriver.xhtml │ │ │ │ @@ -56,112 +56,112 @@ │ │ │ │

    Like a port program, the port communicates with an Erlang process. All │ │ │ │ communication goes through one Erlang process that is the connected process of │ │ │ │ the port driver. Terminating this process closes the port driver.

    Before the port is created, the driver must be loaded. This is done with the │ │ │ │ function erl_ddll:load_driver/2, with the name of the shared library as │ │ │ │ argument.

    The port is then created using the BIF open_port/2, with the │ │ │ │ tuple {spawn, DriverName} as the first argument. The string SharedLib is the │ │ │ │ name of the port driver. The second argument is a list of options, none in this │ │ │ │ -case:

    -module(complex5).
    │ │ │ │ --export([start/1, init/1]).
    │ │ │ │ +case:

    -module(complex5).
    │ │ │ │ +-export([start/1, init/1]).
    │ │ │ │  
    │ │ │ │ -start(SharedLib) ->
    │ │ │ │ -    case erl_ddll:load_driver(".", SharedLib) of
    │ │ │ │ +start(SharedLib) ->
    │ │ │ │ +    case erl_ddll:load_driver(".", SharedLib) of
    │ │ │ │          ok -> ok;
    │ │ │ │ -        {error, already_loaded} -> ok;
    │ │ │ │ -        _ -> exit({error, could_not_load_driver})
    │ │ │ │ +        {error, already_loaded} -> ok;
    │ │ │ │ +        _ -> exit({error, could_not_load_driver})
    │ │ │ │      end,
    │ │ │ │ -    spawn(?MODULE, init, [SharedLib]).
    │ │ │ │ +    spawn(?MODULE, init, [SharedLib]).
    │ │ │ │  
    │ │ │ │ -init(SharedLib) ->
    │ │ │ │ -  register(complex, self()),
    │ │ │ │ -  Port = open_port({spawn, SharedLib}, []),
    │ │ │ │ -  loop(Port).

    Now complex5:foo/1 and complex5:bar/1 can be implemented. Both send a │ │ │ │ -message to the complex process and receive the following reply:

    foo(X) ->
    │ │ │ │ -    call_port({foo, X}).
    │ │ │ │ -bar(Y) ->
    │ │ │ │ -    call_port({bar, Y}).
    │ │ │ │ +init(SharedLib) ->
    │ │ │ │ +  register(complex, self()),
    │ │ │ │ +  Port = open_port({spawn, SharedLib}, []),
    │ │ │ │ +  loop(Port).

    Now complex5:foo/1 and complex5:bar/1 can be implemented. Both send a │ │ │ │ +message to the complex process and receive the following reply:

    foo(X) ->
    │ │ │ │ +    call_port({foo, X}).
    │ │ │ │ +bar(Y) ->
    │ │ │ │ +    call_port({bar, Y}).
    │ │ │ │  
    │ │ │ │ -call_port(Msg) ->
    │ │ │ │ -    complex ! {call, self(), Msg},
    │ │ │ │ +call_port(Msg) ->
    │ │ │ │ +    complex ! {call, self(), Msg},
    │ │ │ │      receive
    │ │ │ │ -        {complex, Result} ->
    │ │ │ │ +        {complex, Result} ->
    │ │ │ │              Result
    │ │ │ │ -    end.

    The complex process performs the following:

    • Encodes the message into a sequence of bytes.
    • Sends it to the port.
    • Waits for a reply.
    • Decodes the reply.
    • Sends it back to the caller:
    loop(Port) ->
    │ │ │ │ +    end.

    The complex process performs the following:

    • Encodes the message into a sequence of bytes.
    • Sends it to the port.
    • Waits for a reply.
    • Decodes the reply.
    • Sends it back to the caller:
    loop(Port) ->
    │ │ │ │      receive
    │ │ │ │ -        {call, Caller, Msg} ->
    │ │ │ │ -            Port ! {self(), {command, encode(Msg)}},
    │ │ │ │ +        {call, Caller, Msg} ->
    │ │ │ │ +            Port ! {self(), {command, encode(Msg)}},
    │ │ │ │              receive
    │ │ │ │ -                {Port, {data, Data}} ->
    │ │ │ │ -                    Caller ! {complex, decode(Data)}
    │ │ │ │ +                {Port, {data, Data}} ->
    │ │ │ │ +                    Caller ! {complex, decode(Data)}
    │ │ │ │              end,
    │ │ │ │ -            loop(Port)
    │ │ │ │ +            loop(Port)
    │ │ │ │      end.

    Assuming that both the arguments and the results from the C functions are less │ │ │ │ than 256, a simple encoding/decoding scheme is employed. In this scheme, foo │ │ │ │ is represented by byte 1, bar is represented by 2, and the argument/result is │ │ │ │ -represented by a single byte as well:

    encode({foo, X}) -> [1, X];
    │ │ │ │ -encode({bar, Y}) -> [2, Y].
    │ │ │ │ +represented by a single byte as well:

    encode({foo, X}) -> [1, X];
    │ │ │ │ +encode({bar, Y}) -> [2, Y].
    │ │ │ │  
    │ │ │ │ -decode([Int]) -> Int.

    The resulting Erlang program, including functions for stopping the port and │ │ │ │ +decode([Int]) -> Int.

    The resulting Erlang program, including functions for stopping the port and │ │ │ │ detecting port failures, is as follows:

    
    │ │ │ │ --module(complex5).
    │ │ │ │ --export([start/1, stop/0, init/1]).
    │ │ │ │ --export([foo/1, bar/1]).
    │ │ │ │ +-module(complex5).
    │ │ │ │ +-export([start/1, stop/0, init/1]).
    │ │ │ │ +-export([foo/1, bar/1]).
    │ │ │ │  
    │ │ │ │ -start(SharedLib) ->
    │ │ │ │ -    case erl_ddll:load_driver(".", SharedLib) of
    │ │ │ │ +start(SharedLib) ->
    │ │ │ │ +    case erl_ddll:load_driver(".", SharedLib) of
    │ │ │ │  	ok -> ok;
    │ │ │ │ -	{error, already_loaded} -> ok;
    │ │ │ │ -	_ -> exit({error, could_not_load_driver})
    │ │ │ │ +	{error, already_loaded} -> ok;
    │ │ │ │ +	_ -> exit({error, could_not_load_driver})
    │ │ │ │      end,
    │ │ │ │ -    spawn(?MODULE, init, [SharedLib]).
    │ │ │ │ +    spawn(?MODULE, init, [SharedLib]).
    │ │ │ │  
    │ │ │ │ -init(SharedLib) ->
    │ │ │ │ -    register(complex, self()),
    │ │ │ │ -    Port = open_port({spawn, SharedLib}, []),
    │ │ │ │ -    loop(Port).
    │ │ │ │ +init(SharedLib) ->
    │ │ │ │ +    register(complex, self()),
    │ │ │ │ +    Port = open_port({spawn, SharedLib}, []),
    │ │ │ │ +    loop(Port).
    │ │ │ │  
    │ │ │ │ -stop() ->
    │ │ │ │ +stop() ->
    │ │ │ │      complex ! stop.
    │ │ │ │  
    │ │ │ │ -foo(X) ->
    │ │ │ │ -    call_port({foo, X}).
    │ │ │ │ -bar(Y) ->
    │ │ │ │ -    call_port({bar, Y}).
    │ │ │ │ +foo(X) ->
    │ │ │ │ +    call_port({foo, X}).
    │ │ │ │ +bar(Y) ->
    │ │ │ │ +    call_port({bar, Y}).
    │ │ │ │  
    │ │ │ │ -call_port(Msg) ->
    │ │ │ │ -    complex ! {call, self(), Msg},
    │ │ │ │ +call_port(Msg) ->
    │ │ │ │ +    complex ! {call, self(), Msg},
    │ │ │ │      receive
    │ │ │ │ -	{complex, Result} ->
    │ │ │ │ +	{complex, Result} ->
    │ │ │ │  	    Result
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -loop(Port) ->
    │ │ │ │ +loop(Port) ->
    │ │ │ │      receive
    │ │ │ │ -	{call, Caller, Msg} ->
    │ │ │ │ -	    Port ! {self(), {command, encode(Msg)}},
    │ │ │ │ +	{call, Caller, Msg} ->
    │ │ │ │ +	    Port ! {self(), {command, encode(Msg)}},
    │ │ │ │  	    receive
    │ │ │ │ -		{Port, {data, Data}} ->
    │ │ │ │ -		    Caller ! {complex, decode(Data)}
    │ │ │ │ +		{Port, {data, Data}} ->
    │ │ │ │ +		    Caller ! {complex, decode(Data)}
    │ │ │ │  	    end,
    │ │ │ │ -	    loop(Port);
    │ │ │ │ +	    loop(Port);
    │ │ │ │  	stop ->
    │ │ │ │ -	    Port ! {self(), close},
    │ │ │ │ +	    Port ! {self(), close},
    │ │ │ │  	    receive
    │ │ │ │ -		{Port, closed} ->
    │ │ │ │ -		    exit(normal)
    │ │ │ │ +		{Port, closed} ->
    │ │ │ │ +		    exit(normal)
    │ │ │ │  	    end;
    │ │ │ │ -	{'EXIT', Port, Reason} ->
    │ │ │ │ -	    io:format("~p ~n", [Reason]),
    │ │ │ │ -	    exit(port_terminated)
    │ │ │ │ +	{'EXIT', Port, Reason} ->
    │ │ │ │ +	    io:format("~p ~n", [Reason]),
    │ │ │ │ +	    exit(port_terminated)
    │ │ │ │      end.
    │ │ │ │  
    │ │ │ │ -encode({foo, X}) -> [1, X];
    │ │ │ │ -encode({bar, Y}) -> [2, Y].
    │ │ │ │ +encode({foo, X}) -> [1, X];
    │ │ │ │ +encode({bar, Y}) -> [2, Y].
    │ │ │ │  
    │ │ │ │ -decode([Int]) -> Int.

    │ │ │ │ +decode([Int]) -> Int.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ C Driver │ │ │ │

    │ │ │ │

    The C driver is a module that is compiled and linked into a shared library. It │ │ │ │ uses a driver structure and includes the header file erl_driver.h.

    The driver structure is filled with the driver name and function pointers. It is │ │ │ │ @@ -178,44 +178,44 @@ │ │ │ │ reference.

    It is not a good idea to use a global variable as the port driver can be spawned │ │ │ │ by multiple Erlang processes. This driver-structure is to be instantiated │ │ │ │ multiple times:

    /* port_driver.c */
    │ │ │ │  
    │ │ │ │  #include <stdio.h>
    │ │ │ │  #include "erl_driver.h"
    │ │ │ │  
    │ │ │ │ -typedef struct {
    │ │ │ │ +typedef struct {
    │ │ │ │      ErlDrvPort port;
    │ │ │ │ -} example_data;
    │ │ │ │ +} example_data;
    │ │ │ │  
    │ │ │ │ -static ErlDrvData example_drv_start(ErlDrvPort port, char *buff)
    │ │ │ │ -{
    │ │ │ │ -    example_data* d = (example_data*)driver_alloc(sizeof(example_data));
    │ │ │ │ +static ErlDrvData example_drv_start(ErlDrvPort port, char *buff)
    │ │ │ │ +{
    │ │ │ │ +    example_data* d = (example_data*)driver_alloc(sizeof(example_data));
    │ │ │ │      d->port = port;
    │ │ │ │ -    return (ErlDrvData)d;
    │ │ │ │ -}
    │ │ │ │ +    return (ErlDrvData)d;
    │ │ │ │ +}
    │ │ │ │  
    │ │ │ │ -static void example_drv_stop(ErlDrvData handle)
    │ │ │ │ -{
    │ │ │ │ -    driver_free((char*)handle);
    │ │ │ │ -}
    │ │ │ │ -
    │ │ │ │ -static void example_drv_output(ErlDrvData handle, char *buff,
    │ │ │ │ -			       ErlDrvSizeT bufflen)
    │ │ │ │ -{
    │ │ │ │ -    example_data* d = (example_data*)handle;
    │ │ │ │ -    char fn = buff[0], arg = buff[1], res;
    │ │ │ │ -    if (fn == 1) {
    │ │ │ │ -      res = foo(arg);
    │ │ │ │ -    } else if (fn == 2) {
    │ │ │ │ -      res = bar(arg);
    │ │ │ │ -    }
    │ │ │ │ -    driver_output(d->port, &res, 1);
    │ │ │ │ -}
    │ │ │ │ +static void example_drv_stop(ErlDrvData handle)
    │ │ │ │ +{
    │ │ │ │ +    driver_free((char*)handle);
    │ │ │ │ +}
    │ │ │ │ +
    │ │ │ │ +static void example_drv_output(ErlDrvData handle, char *buff,
    │ │ │ │ +			       ErlDrvSizeT bufflen)
    │ │ │ │ +{
    │ │ │ │ +    example_data* d = (example_data*)handle;
    │ │ │ │ +    char fn = buff[0], arg = buff[1], res;
    │ │ │ │ +    if (fn == 1) {
    │ │ │ │ +      res = foo(arg);
    │ │ │ │ +    } else if (fn == 2) {
    │ │ │ │ +      res = bar(arg);
    │ │ │ │ +    }
    │ │ │ │ +    driver_output(d->port, &res, 1);
    │ │ │ │ +}
    │ │ │ │  
    │ │ │ │ -ErlDrvEntry example_driver_entry = {
    │ │ │ │ +ErlDrvEntry example_driver_entry = {
    │ │ │ │      NULL,			/* F_PTR init, called when driver is loaded */
    │ │ │ │      example_drv_start,		/* L_PTR start, called when port is opened */
    │ │ │ │      example_drv_stop,		/* F_PTR stop, called when port is closed */
    │ │ │ │      example_drv_output,		/* F_PTR output, called when erlang has sent */
    │ │ │ │      NULL,			/* F_PTR ready_input, called when input descriptor ready */
    │ │ │ │      NULL,			/* F_PTR ready_output, called when output descriptor ready */
    │ │ │ │      "example_drv",		/* char *driver_name, the argument to open_port */
    │ │ │ │ @@ -239,35 +239,35 @@
    │ │ │ │  				       set to this value */
    │ │ │ │      0,                          /* int driver_flags, see documentation */
    │ │ │ │      NULL,                       /* void *handle2, reserved for VM use */
    │ │ │ │      NULL,                       /* F_PTR process_exit, called when a
    │ │ │ │  				   monitored process dies */
    │ │ │ │      NULL                        /* F_PTR stop_select, called to close an
    │ │ │ │  				   event object */
    │ │ │ │ -};
    │ │ │ │ +};
    │ │ │ │  
    │ │ │ │ -DRIVER_INIT(example_drv) /* must match name in driver_entry */
    │ │ │ │ -{
    │ │ │ │ +DRIVER_INIT(example_drv) /* must match name in driver_entry */
    │ │ │ │ +{
    │ │ │ │      return &example_driver_entry;
    │ │ │ │ -}

    │ │ │ │ +}

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ 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 │ │ │ │ @@ -156,104 +156,104 @@ │ │ │ │ follows:

    /* erl_comm.c */
    │ │ │ │  
    │ │ │ │  #include <stdio.h>
    │ │ │ │  #include <unistd.h>
    │ │ │ │  
    │ │ │ │  typedef unsigned char byte;
    │ │ │ │  
    │ │ │ │ -int read_exact(byte *buf, int len)
    │ │ │ │ -{
    │ │ │ │ +int read_exact(byte *buf, int len)
    │ │ │ │ +{
    │ │ │ │    int i, got=0;
    │ │ │ │  
    │ │ │ │ -  do {
    │ │ │ │ -      if ((i = read(0, buf+got, len-got)) <= 0){
    │ │ │ │ -          return(i);
    │ │ │ │ -      }
    │ │ │ │ +  do {
    │ │ │ │ +      if ((i = read(0, buf+got, len-got)) <= 0){
    │ │ │ │ +          return(i);
    │ │ │ │ +      }
    │ │ │ │      got += i;
    │ │ │ │ -  } while (got<len);
    │ │ │ │ +  } while (got<len);
    │ │ │ │  
    │ │ │ │ -  return(len);
    │ │ │ │ -}
    │ │ │ │ +  return(len);
    │ │ │ │ +}
    │ │ │ │  
    │ │ │ │ -int write_exact(byte *buf, int len)
    │ │ │ │ -{
    │ │ │ │ +int write_exact(byte *buf, int len)
    │ │ │ │ +{
    │ │ │ │    int i, wrote = 0;
    │ │ │ │  
    │ │ │ │ -  do {
    │ │ │ │ -    if ((i = write(1, buf+wrote, len-wrote)) <= 0)
    │ │ │ │ -      return (i);
    │ │ │ │ +  do {
    │ │ │ │ +    if ((i = write(1, buf+wrote, len-wrote)) <= 0)
    │ │ │ │ +      return (i);
    │ │ │ │      wrote += i;
    │ │ │ │ -  } while (wrote<len);
    │ │ │ │ +  } while (wrote<len);
    │ │ │ │  
    │ │ │ │ -  return (len);
    │ │ │ │ -}
    │ │ │ │ +  return (len);
    │ │ │ │ +}
    │ │ │ │  
    │ │ │ │ -int read_cmd(byte *buf)
    │ │ │ │ -{
    │ │ │ │ +int read_cmd(byte *buf)
    │ │ │ │ +{
    │ │ │ │    int len;
    │ │ │ │  
    │ │ │ │ -  if (read_exact(buf, 2) != 2)
    │ │ │ │ -    return(-1);
    │ │ │ │ -  len = (buf[0] << 8) | buf[1];
    │ │ │ │ -  return read_exact(buf, len);
    │ │ │ │ -}
    │ │ │ │ +  if (read_exact(buf, 2) != 2)
    │ │ │ │ +    return(-1);
    │ │ │ │ +  len = (buf[0] << 8) | buf[1];
    │ │ │ │ +  return read_exact(buf, len);
    │ │ │ │ +}
    │ │ │ │  
    │ │ │ │ -int write_cmd(byte *buf, int len)
    │ │ │ │ -{
    │ │ │ │ +int write_cmd(byte *buf, int len)
    │ │ │ │ +{
    │ │ │ │    byte li;
    │ │ │ │  
    │ │ │ │ -  li = (len >> 8) & 0xff;
    │ │ │ │ -  write_exact(&li, 1);
    │ │ │ │ +  li = (len >> 8) & 0xff;
    │ │ │ │ +  write_exact(&li, 1);
    │ │ │ │  
    │ │ │ │    li = len & 0xff;
    │ │ │ │ -  write_exact(&li, 1);
    │ │ │ │ +  write_exact(&li, 1);
    │ │ │ │  
    │ │ │ │ -  return write_exact(buf, len);
    │ │ │ │ -}

    Notice that stdin and stdout are for buffered input/output and must not be │ │ │ │ + return write_exact(buf, len); │ │ │ │ +}

    Notice that stdin and stdout are for buffered input/output and must not be │ │ │ │ used for the communication with Erlang.

    In the main function, the C program is to listen for a message from Erlang │ │ │ │ and, according to the selected encoding/decoding scheme, use the first byte to │ │ │ │ determine which function to call and the second byte as argument to the │ │ │ │ function. The result of calling the function is then to be sent back to Erlang:

    /* port.c */
    │ │ │ │  
    │ │ │ │  typedef unsigned char byte;
    │ │ │ │  
    │ │ │ │ -int main() {
    │ │ │ │ +int main() {
    │ │ │ │    int fn, arg, res;
    │ │ │ │ -  byte buf[100];
    │ │ │ │ +  byte buf[100];
    │ │ │ │  
    │ │ │ │ -  while (read_cmd(buf) > 0) {
    │ │ │ │ -    fn = buf[0];
    │ │ │ │ -    arg = buf[1];
    │ │ │ │ -
    │ │ │ │ -    if (fn == 1) {
    │ │ │ │ -      res = foo(arg);
    │ │ │ │ -    } else if (fn == 2) {
    │ │ │ │ -      res = bar(arg);
    │ │ │ │ -    }
    │ │ │ │ -
    │ │ │ │ -    buf[0] = res;
    │ │ │ │ -    write_cmd(buf, 1);
    │ │ │ │ -  }
    │ │ │ │ -}

    Notice that the C program is in a while-loop, checking for the return value │ │ │ │ + while (read_cmd(buf) > 0) { │ │ │ │ + fn = buf[0]; │ │ │ │ + arg = buf[1]; │ │ │ │ + │ │ │ │ + if (fn == 1) { │ │ │ │ + res = foo(arg); │ │ │ │ + } else if (fn == 2) { │ │ │ │ + res = bar(arg); │ │ │ │ + } │ │ │ │ + │ │ │ │ + buf[0] = res; │ │ │ │ + write_cmd(buf, 1); │ │ │ │ + } │ │ │ │ +}

    Notice that the C program is in a while-loop, checking for the return value │ │ │ │ of read_cmd/1. This is because the C program must detect when the port closes │ │ │ │ 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.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -341,118 +341,118 @@ │ │ │ │ prim_app application. Normally, the restart_application instruction in the │ │ │ │ .appup file for prim_app would be used.

    However, if this is done and a .relup file is generated, not only would it │ │ │ │ contain instructions for restarting (that is, removing and adding) prim_app, │ │ │ │ it would also contain instructions for starting ch_app (and stopping it, in │ │ │ │ the case of downgrade). This is because ch_app is included in the new .rel │ │ │ │ file, but not in the old one.

    Instead, a correct relup file can be created manually, either from scratch or │ │ │ │ by editing the generated version. The instructions for starting/stopping │ │ │ │ -ch_app are replaced by instructions for loading/unloading the application:

    {"B",
    │ │ │ │ - [{"A",
    │ │ │ │ -   [],
    │ │ │ │ -   [{load_object_code,{ch_app,"1",[ch_sup,ch3]}},
    │ │ │ │ -    {load_object_code,{prim_app,"2",[prim_app,prim_sup]}},
    │ │ │ │ +ch_app are replaced by instructions for loading/unloading the application:

    {"B",
    │ │ │ │ + [{"A",
    │ │ │ │ +   [],
    │ │ │ │ +   [{load_object_code,{ch_app,"1",[ch_sup,ch3]}},
    │ │ │ │ +    {load_object_code,{prim_app,"2",[prim_app,prim_sup]}},
    │ │ │ │      point_of_no_return,
    │ │ │ │ -    {apply,{application,stop,[prim_app]}},
    │ │ │ │ -    {remove,{prim_app,brutal_purge,brutal_purge}},
    │ │ │ │ -    {remove,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ │ -    {purge,[prim_app,prim_sup]},
    │ │ │ │ -    {load,{prim_app,brutal_purge,brutal_purge}},
    │ │ │ │ -    {load,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ │ -    {load,{ch_sup,brutal_purge,brutal_purge}},
    │ │ │ │ -    {load,{ch3,brutal_purge,brutal_purge}},
    │ │ │ │ -    {apply,{application,load,[ch_app]}},
    │ │ │ │ -    {apply,{application,start,[prim_app,permanent]}}]}],
    │ │ │ │ - [{"A",
    │ │ │ │ -   [],
    │ │ │ │ -   [{load_object_code,{prim_app,"1",[prim_app,prim_sup]}},
    │ │ │ │ +    {apply,{application,stop,[prim_app]}},
    │ │ │ │ +    {remove,{prim_app,brutal_purge,brutal_purge}},
    │ │ │ │ +    {remove,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ │ +    {purge,[prim_app,prim_sup]},
    │ │ │ │ +    {load,{prim_app,brutal_purge,brutal_purge}},
    │ │ │ │ +    {load,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ │ +    {load,{ch_sup,brutal_purge,brutal_purge}},
    │ │ │ │ +    {load,{ch3,brutal_purge,brutal_purge}},
    │ │ │ │ +    {apply,{application,load,[ch_app]}},
    │ │ │ │ +    {apply,{application,start,[prim_app,permanent]}}]}],
    │ │ │ │ + [{"A",
    │ │ │ │ +   [],
    │ │ │ │ +   [{load_object_code,{prim_app,"1",[prim_app,prim_sup]}},
    │ │ │ │      point_of_no_return,
    │ │ │ │ -    {apply,{application,stop,[prim_app]}},
    │ │ │ │ -    {apply,{application,unload,[ch_app]}},
    │ │ │ │ -    {remove,{ch_sup,brutal_purge,brutal_purge}},
    │ │ │ │ -    {remove,{ch3,brutal_purge,brutal_purge}},
    │ │ │ │ -    {purge,[ch_sup,ch3]},
    │ │ │ │ -    {remove,{prim_app,brutal_purge,brutal_purge}},
    │ │ │ │ -    {remove,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ │ -    {purge,[prim_app,prim_sup]},
    │ │ │ │ -    {load,{prim_app,brutal_purge,brutal_purge}},
    │ │ │ │ -    {load,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ │ -    {apply,{application,start,[prim_app,permanent]}}]}]
    │ │ │ │ -}.

    │ │ │ │ + {apply,{application,stop,[prim_app]}}, │ │ │ │ + {apply,{application,unload,[ch_app]}}, │ │ │ │ + {remove,{ch_sup,brutal_purge,brutal_purge}}, │ │ │ │ + {remove,{ch3,brutal_purge,brutal_purge}}, │ │ │ │ + {purge,[ch_sup,ch3]}, │ │ │ │ + {remove,{prim_app,brutal_purge,brutal_purge}}, │ │ │ │ + {remove,{prim_sup,brutal_purge,brutal_purge}}, │ │ │ │ + {purge,[prim_app,prim_sup]}, │ │ │ │ + {load,{prim_app,brutal_purge,brutal_purge}}, │ │ │ │ + {load,{prim_sup,brutal_purge,brutal_purge}}, │ │ │ │ + {apply,{application,start,[prim_app,permanent]}}]}] │ │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ Supervisor Change │ │ │ │

    │ │ │ │

    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.

    │ │ │ │ │ │ │ │ │ @@ -436,118 +436,118 @@ │ │ │ prim_app application. Normally, the restart_application instruction in the │ │ │ .appup file for prim_app would be used.

    However, if this is done and a .relup file is generated, not only would it │ │ │ contain instructions for restarting (that is, removing and adding) prim_app, │ │ │ it would also contain instructions for starting ch_app (and stopping it, in │ │ │ the case of downgrade). This is because ch_app is included in the new .rel │ │ │ file, but not in the old one.

    Instead, a correct relup file can be created manually, either from scratch or │ │ │ by editing the generated version. The instructions for starting/stopping │ │ │ -ch_app are replaced by instructions for loading/unloading the application:

    {"B",
    │ │ │ - [{"A",
    │ │ │ -   [],
    │ │ │ -   [{load_object_code,{ch_app,"1",[ch_sup,ch3]}},
    │ │ │ -    {load_object_code,{prim_app,"2",[prim_app,prim_sup]}},
    │ │ │ +ch_app are replaced by instructions for loading/unloading the application:

    {"B",
    │ │ │ + [{"A",
    │ │ │ +   [],
    │ │ │ +   [{load_object_code,{ch_app,"1",[ch_sup,ch3]}},
    │ │ │ +    {load_object_code,{prim_app,"2",[prim_app,prim_sup]}},
    │ │ │      point_of_no_return,
    │ │ │ -    {apply,{application,stop,[prim_app]}},
    │ │ │ -    {remove,{prim_app,brutal_purge,brutal_purge}},
    │ │ │ -    {remove,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ -    {purge,[prim_app,prim_sup]},
    │ │ │ -    {load,{prim_app,brutal_purge,brutal_purge}},
    │ │ │ -    {load,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ -    {load,{ch_sup,brutal_purge,brutal_purge}},
    │ │ │ -    {load,{ch3,brutal_purge,brutal_purge}},
    │ │ │ -    {apply,{application,load,[ch_app]}},
    │ │ │ -    {apply,{application,start,[prim_app,permanent]}}]}],
    │ │ │ - [{"A",
    │ │ │ -   [],
    │ │ │ -   [{load_object_code,{prim_app,"1",[prim_app,prim_sup]}},
    │ │ │ +    {apply,{application,stop,[prim_app]}},
    │ │ │ +    {remove,{prim_app,brutal_purge,brutal_purge}},
    │ │ │ +    {remove,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ +    {purge,[prim_app,prim_sup]},
    │ │ │ +    {load,{prim_app,brutal_purge,brutal_purge}},
    │ │ │ +    {load,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ +    {load,{ch_sup,brutal_purge,brutal_purge}},
    │ │ │ +    {load,{ch3,brutal_purge,brutal_purge}},
    │ │ │ +    {apply,{application,load,[ch_app]}},
    │ │ │ +    {apply,{application,start,[prim_app,permanent]}}]}],
    │ │ │ + [{"A",
    │ │ │ +   [],
    │ │ │ +   [{load_object_code,{prim_app,"1",[prim_app,prim_sup]}},
    │ │ │      point_of_no_return,
    │ │ │ -    {apply,{application,stop,[prim_app]}},
    │ │ │ -    {apply,{application,unload,[ch_app]}},
    │ │ │ -    {remove,{ch_sup,brutal_purge,brutal_purge}},
    │ │ │ -    {remove,{ch3,brutal_purge,brutal_purge}},
    │ │ │ -    {purge,[ch_sup,ch3]},
    │ │ │ -    {remove,{prim_app,brutal_purge,brutal_purge}},
    │ │ │ -    {remove,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ -    {purge,[prim_app,prim_sup]},
    │ │ │ -    {load,{prim_app,brutal_purge,brutal_purge}},
    │ │ │ -    {load,{prim_sup,brutal_purge,brutal_purge}},
    │ │ │ -    {apply,{application,start,[prim_app,permanent]}}]}]
    │ │ │ -}.

    │ │ │ + {apply,{application,stop,[prim_app]}}, │ │ │ + {apply,{application,unload,[ch_app]}}, │ │ │ + {remove,{ch_sup,brutal_purge,brutal_purge}}, │ │ │ + {remove,{ch3,brutal_purge,brutal_purge}}, │ │ │ + {purge,[ch_sup,ch3]}, │ │ │ + {remove,{prim_app,brutal_purge,brutal_purge}}, │ │ │ + {remove,{prim_sup,brutal_purge,brutal_purge}}, │ │ │ + {purge,[prim_app,prim_sup]}, │ │ │ + {load,{prim_app,brutal_purge,brutal_purge}}, │ │ │ + {load,{prim_sup,brutal_purge,brutal_purge}}, │ │ │ + {apply,{application,start,[prim_app,permanent]}}]}] │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ Supervisor Change │ │ │

    │ │ │

    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 │ │ │ @@ -251,107 +251,107 @@ │ │ │ follows:

          /* erl_comm.c */
          │ │ │  
          │ │ │  #include <stdio.h>
          │ │ │  #include <unistd.h>
          │ │ │  
          │ │ │  typedef unsigned char byte;
          │ │ │  
          │ │ │ -int read_exact(byte *buf, int len)
          │ │ │ -{
          │ │ │ +int read_exact(byte *buf, int len)
          │ │ │ +{
          │ │ │    int i, got=0;
          │ │ │  
          │ │ │ -  do {
          │ │ │ -      if ((i = read(0, buf+got, len-got)) <= 0){
          │ │ │ -          return(i);
          │ │ │ -      }
          │ │ │ +  do {
          │ │ │ +      if ((i = read(0, buf+got, len-got)) <= 0){
          │ │ │ +          return(i);
          │ │ │ +      }
          │ │ │      got += i;
          │ │ │ -  } while (got<len);
          │ │ │ +  } while (got<len);
          │ │ │  
          │ │ │ -  return(len);
          │ │ │ -}
          │ │ │ +  return(len);
          │ │ │ +}
          │ │ │  
          │ │ │ -int write_exact(byte *buf, int len)
          │ │ │ -{
          │ │ │ +int write_exact(byte *buf, int len)
          │ │ │ +{
          │ │ │    int i, wrote = 0;
          │ │ │  
          │ │ │ -  do {
          │ │ │ -    if ((i = write(1, buf+wrote, len-wrote)) <= 0)
          │ │ │ -      return (i);
          │ │ │ +  do {
          │ │ │ +    if ((i = write(1, buf+wrote, len-wrote)) <= 0)
          │ │ │ +      return (i);
          │ │ │      wrote += i;
          │ │ │ -  } while (wrote<len);
          │ │ │ +  } while (wrote<len);
          │ │ │  
          │ │ │ -  return (len);
          │ │ │ -}
          │ │ │ +  return (len);
          │ │ │ +}
          │ │ │  
          │ │ │ -int read_cmd(byte *buf)
          │ │ │ -{
          │ │ │ +int read_cmd(byte *buf)
          │ │ │ +{
          │ │ │    int len;
          │ │ │  
          │ │ │ -  if (read_exact(buf, 2) != 2)
          │ │ │ -    return(-1);
          │ │ │ -  len = (buf[0] << 8) | buf[1];
          │ │ │ -  return read_exact(buf, len);
          │ │ │ -}
          │ │ │ +  if (read_exact(buf, 2) != 2)
          │ │ │ +    return(-1);
          │ │ │ +  len = (buf[0] << 8) | buf[1];
          │ │ │ +  return read_exact(buf, len);
          │ │ │ +}
          │ │ │  
          │ │ │ -int write_cmd(byte *buf, int len)
          │ │ │ -{
          │ │ │ +int write_cmd(byte *buf, int len)
          │ │ │ +{
          │ │ │    byte li;
          │ │ │  
          │ │ │ -  li = (len >> 8) & 0xff;
          │ │ │ -  write_exact(&li, 1);
          │ │ │ +  li = (len >> 8) & 0xff;
          │ │ │ +  write_exact(&li, 1);
          │ │ │  
          │ │ │    li = len & 0xff;
          │ │ │ -  write_exact(&li, 1);
          │ │ │ +  write_exact(&li, 1);
          │ │ │  
          │ │ │ -  return write_exact(buf, len);
          │ │ │ -}

          Notice that stdin and stdout are for buffered input/output and must not be │ │ │ + return write_exact(buf, len); │ │ │ +}

          Notice that stdin and stdout are for buffered input/output and must not be │ │ │ used for the communication with Erlang.

          In the main function, the C program is to listen for a message from Erlang │ │ │ and, according to the selected encoding/decoding scheme, use the first byte to │ │ │ determine which function to call and the second byte as argument to the │ │ │ function. The result of calling the function is then to be sent back to Erlang:

          /* port.c */
          │ │ │  
          │ │ │  typedef unsigned char byte;
          │ │ │  
          │ │ │ -int main() {
          │ │ │ +int main() {
          │ │ │    int fn, arg, res;
          │ │ │ -  byte buf[100];
          │ │ │ +  byte buf[100];
          │ │ │  
          │ │ │ -  while (read_cmd(buf) > 0) {
          │ │ │ -    fn = buf[0];
          │ │ │ -    arg = buf[1];
          │ │ │ -
          │ │ │ -    if (fn == 1) {
          │ │ │ -      res = foo(arg);
          │ │ │ -    } else if (fn == 2) {
          │ │ │ -      res = bar(arg);
          │ │ │ -    }
          │ │ │ -
          │ │ │ -    buf[0] = res;
          │ │ │ -    write_cmd(buf, 1);
          │ │ │ -  }
          │ │ │ -}

          Notice that the C program is in a while-loop, checking for the return value │ │ │ + while (read_cmd(buf) > 0) { │ │ │ + fn = buf[0]; │ │ │ + arg = buf[1]; │ │ │ + │ │ │ + if (fn == 1) { │ │ │ + res = foo(arg); │ │ │ + } else if (fn == 2) { │ │ │ + res = bar(arg); │ │ │ + } │ │ │ + │ │ │ + buf[0] = res; │ │ │ + write_cmd(buf, 1); │ │ │ + } │ │ │ +}

          Notice that the C program is in a while-loop, checking for the return value │ │ │ of read_cmd/1. This is because the C program must detect when the port closes │ │ │ 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 │ │ │ @@ -273,44 +273,44 @@ │ │ │ reference.

          It is not a good idea to use a global variable as the port driver can be spawned │ │ │ by multiple Erlang processes. This driver-structure is to be instantiated │ │ │ multiple times:

          /* port_driver.c */
          │ │ │  
          │ │ │  #include <stdio.h>
          │ │ │  #include "erl_driver.h"
          │ │ │  
          │ │ │ -typedef struct {
          │ │ │ +typedef struct {
          │ │ │      ErlDrvPort port;
          │ │ │ -} example_data;
          │ │ │ +} example_data;
          │ │ │  
          │ │ │ -static ErlDrvData example_drv_start(ErlDrvPort port, char *buff)
          │ │ │ -{
          │ │ │ -    example_data* d = (example_data*)driver_alloc(sizeof(example_data));
          │ │ │ +static ErlDrvData example_drv_start(ErlDrvPort port, char *buff)
          │ │ │ +{
          │ │ │ +    example_data* d = (example_data*)driver_alloc(sizeof(example_data));
          │ │ │      d->port = port;
          │ │ │ -    return (ErlDrvData)d;
          │ │ │ -}
          │ │ │ +    return (ErlDrvData)d;
          │ │ │ +}
          │ │ │  
          │ │ │ -static void example_drv_stop(ErlDrvData handle)
          │ │ │ -{
          │ │ │ -    driver_free((char*)handle);
          │ │ │ -}
          │ │ │ -
          │ │ │ -static void example_drv_output(ErlDrvData handle, char *buff,
          │ │ │ -			       ErlDrvSizeT bufflen)
          │ │ │ -{
          │ │ │ -    example_data* d = (example_data*)handle;
          │ │ │ -    char fn = buff[0], arg = buff[1], res;
          │ │ │ -    if (fn == 1) {
          │ │ │ -      res = foo(arg);
          │ │ │ -    } else if (fn == 2) {
          │ │ │ -      res = bar(arg);
          │ │ │ -    }
          │ │ │ -    driver_output(d->port, &res, 1);
          │ │ │ -}
          │ │ │ +static void example_drv_stop(ErlDrvData handle)
          │ │ │ +{
          │ │ │ +    driver_free((char*)handle);
          │ │ │ +}
          │ │ │ +
          │ │ │ +static void example_drv_output(ErlDrvData handle, char *buff,
          │ │ │ +			       ErlDrvSizeT bufflen)
          │ │ │ +{
          │ │ │ +    example_data* d = (example_data*)handle;
          │ │ │ +    char fn = buff[0], arg = buff[1], res;
          │ │ │ +    if (fn == 1) {
          │ │ │ +      res = foo(arg);
          │ │ │ +    } else if (fn == 2) {
          │ │ │ +      res = bar(arg);
          │ │ │ +    }
          │ │ │ +    driver_output(d->port, &res, 1);
          │ │ │ +}
          │ │ │  
          │ │ │ -ErlDrvEntry example_driver_entry = {
          │ │ │ +ErlDrvEntry example_driver_entry = {
          │ │ │      NULL,			/* F_PTR init, called when driver is loaded */
          │ │ │      example_drv_start,		/* L_PTR start, called when port is opened */
          │ │ │      example_drv_stop,		/* F_PTR stop, called when port is closed */
          │ │ │      example_drv_output,		/* F_PTR output, called when erlang has sent */
          │ │ │      NULL,			/* F_PTR ready_input, called when input descriptor ready */
          │ │ │      NULL,			/* F_PTR ready_output, called when output descriptor ready */
          │ │ │      "example_drv",		/* char *driver_name, the argument to open_port */
          │ │ │ @@ -334,38 +334,38 @@
          │ │ │  				       set to this value */
          │ │ │      0,                          /* int driver_flags, see documentation */
          │ │ │      NULL,                       /* void *handle2, reserved for VM use */
          │ │ │      NULL,                       /* F_PTR process_exit, called when a
          │ │ │  				   monitored process dies */
          │ │ │      NULL                        /* F_PTR stop_select, called to close an
          │ │ │  				   event object */
          │ │ │ -};
          │ │ │ +};
          │ │ │  
          │ │ │ -DRIVER_INIT(example_drv) /* must match name in driver_entry */
          │ │ │ -{
          │ │ │ +DRIVER_INIT(example_drv) /* must match name in driver_entry */
          │ │ │ +{
          │ │ │      return &example_driver_entry;
          │ │ │ -}

          │ │ │ +}

          │ │ │ │ │ │ │ │ │ │ │ │ Running the Example │ │ │

          │ │ │

          Step 1. Compile the C code:

          unix> gcc -o example_drv.so -fpic -shared complex.c port_driver.c
          │ │ │  windows> cl -LD -MD -Fe example_drv.dll complex.c port_driver.c

          Step 2. Start Erlang and compile the Erlang code:

          > erl
          │ │ │ -Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
          │ │ │ +Erlang/OTP 26 [erts-14.2] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit:ns]
          │ │ │  
          │ │ │ -Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
          │ │ │ -1> c(complex5).
          │ │ │ -{ok,complex5}

          Step 3. Run the example:

          2> complex5:start("example_drv").
          │ │ │ +Eshell V14.2 (press Ctrl+G to abort, type help(). for help)
          │ │ │ +1> c(complex5).
          │ │ │ +{ok,complex5}

          Step 3. Run the example:

          2> complex5:start("example_drv").
          │ │ │  <0.34.0>
          │ │ │ -3> complex5:foo(3).
          │ │ │ +3> complex5:foo(3).
          │ │ │  4
          │ │ │ -4> complex5:bar(5).
          │ │ │ +4> complex5:bar(5).
          │ │ │  10
          │ │ │ -5> complex5:stop().
          │ │ │ +5> complex5:stop().
          │ │ │  stop
          │ │ │ │ │ │ │ │ │
          │ │ │
          │ │ │ │ │ │ │ │ │ Compilation │ │ │ │ │ │

          Erlang programs must be compiled to object code. The compiler can generate a │ │ │ new file that contains the object code. The current abstract machine, which runs │ │ │ the object code, is called BEAM, therefore the object files get the suffix │ │ │ -.beam. The compiler can also generate a binary which can be loaded directly.

          The compiler is located in the module compile in Compiler.

          compile:file(Module)
          │ │ │ -compile:file(Module, Options)

          The Erlang shell understands the command c(Module), which both compiles and │ │ │ +.beam. The compiler can also generate a binary which can be loaded directly.

          The compiler is located in the module compile in Compiler.

          compile:file(Module)
          │ │ │ +compile:file(Module, Options)

          The Erlang shell understands the command c(Module), which both compiles and │ │ │ loads Module.

          There is also a module make, which provides a set of functions similar to the │ │ │ UNIX type Make functions, see module make in Tools.

          The compiler can also be accessed from the OS prompt using the │ │ │ erl executable in ERTS.

          % erl -compile Module1...ModuleN
          │ │ │  % erl -make

          The erlc program provides way to compile modules from the OS │ │ │ shell, see the erlc executable in ERTS. It │ │ │ understands a number of flags that can be used to define macros, add search │ │ │ paths for include files, and more.

          % erlc <flags> File1.erl...FileN.erl

          │ │ │ @@ -156,54 +156,54 @@ │ │ │ When a module is loaded into the system for the first time, the code becomes │ │ │ 'current'. If then a new instance of the module is loaded, the code of the │ │ │ previous instance becomes 'old' and the new instance becomes 'current'.

          Both old and current code is valid, and can be evaluated concurrently. Fully │ │ │ qualified function calls always refer to current code. Old code can still be │ │ │ evaluated because of processes lingering in the old code.

          If a third instance of the module is loaded, the code server removes (purges) │ │ │ the old code and any processes lingering in it is terminated. Then the third │ │ │ instance becomes 'current' and the previously current code becomes 'old'.

          To change from old code to current code, a process must make a fully qualified │ │ │ -function call.

          Example:

          -module(m).
          │ │ │ --export([loop/0]).
          │ │ │ +function call.

          Example:

          -module(m).
          │ │ │ +-export([loop/0]).
          │ │ │  
          │ │ │ -loop() ->
          │ │ │ +loop() ->
          │ │ │      receive
          │ │ │          code_switch ->
          │ │ │ -            m:loop();
          │ │ │ +            m:loop();
          │ │ │          Msg ->
          │ │ │              ...
          │ │ │ -            loop()
          │ │ │ +            loop()
          │ │ │      end.

          To make the process change code, send the message code_switch to it. The │ │ │ process then makes a fully qualified call to m:loop() and changes to current │ │ │ code. Notice that m:loop/0 must be exported.

          For code replacement of funs to work, use the syntax │ │ │ fun Module:FunctionName/Arity.

          │ │ │ │ │ │ │ │ │ │ │ │ Running a Function When a Module is Loaded │ │ │

          │ │ │

          The -on_load() directive names a function that is to be run automatically when │ │ │ -a module is loaded.

          Its syntax is as follows:

          -on_load(Name/0).

          It is not necessary to export the function. It is called in a freshly spawned │ │ │ +a module is loaded.

          Its syntax is as follows:

          -on_load(Name/0).

          It is not necessary to export the function. It is called in a freshly spawned │ │ │ process (which terminates as soon as the function returns).

          The function must return ok if the module is to become the new current code │ │ │ for the module and become callable.

          Returning any other value or generating an exception causes the new code to be │ │ │ unloaded. If the return value is not an atom, a warning error report is sent to │ │ │ the error logger.

          If there already is current code for the module, that code will remain current │ │ │ and can be called until the on_load function has returned. If the on_load │ │ │ function fails, the current code (if any) will remain current. If there is no │ │ │ current code for a module, any process that makes an external call to the module │ │ │ before the on_load function has finished will be suspended until the on_load │ │ │ function have finished.

          Change

          Before Erlang/OTP 19, if the on_load function failed, any previously current │ │ │ code would become old, essentially leaving the system without any working and │ │ │ reachable instance of the module.

          In embedded mode, first all modules are loaded. Then all on_load functions are │ │ │ called. The system is terminated unless all of the on_load functions return │ │ │ -ok.

          Example:

          -module(m).
          │ │ │ --on_load(load_my_nifs/0).
          │ │ │ +ok.

          Example:

          -module(m).
          │ │ │ +-on_load(load_my_nifs/0).
          │ │ │  
          │ │ │ -load_my_nifs() ->
          │ │ │ +load_my_nifs() ->
          │ │ │      NifPath = ...,    %Set up the path to the NIF library.
          │ │ │      Info = ...,       %Initialize the Info term
          │ │ │ -    erlang:load_nif(NifPath, Info).

          If the call to erlang:load_nif/2 fails, the module is unloaded and a warning │ │ │ + erlang:load_nif(NifPath, Info).

          If the call to erlang:load_nif/2 fails, the module is unloaded and a warning │ │ │ report is sent to the error loader.

          │ │ │

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

          │ │ │ │ │ │ │ │ │ Operator ++ │ │ │

          │ │ │

          The ++ operator copies its left-hand side operand. That is clearly │ │ │ -seen if we do our own implementation in Erlang:

          my_plus_plus([H|T], Tail) ->
          │ │ │ -    [H|my_plus_plus(T, Tail)];
          │ │ │ -my_plus_plus([], Tail) ->
          │ │ │ -    Tail.

          We must be careful how we use ++ in a loop. First is how not to use it:

          DO NOT

          naive_reverse([H|T]) ->
          │ │ │ -    naive_reverse(T) ++ [H];
          │ │ │ -naive_reverse([]) ->
          │ │ │ -    [].

          As the ++ operator copies its left-hand side operand, the growing │ │ │ -result is copied repeatedly, leading to quadratic complexity.

          On the other hand, using ++ in loop like this is perfectly fine:

          OK

          naive_but_ok_reverse(List) ->
          │ │ │ -    naive_but_ok_reverse(List, []).
          │ │ │ +seen if we do our own implementation in Erlang:

          my_plus_plus([H|T], Tail) ->
          │ │ │ +    [H|my_plus_plus(T, Tail)];
          │ │ │ +my_plus_plus([], Tail) ->
          │ │ │ +    Tail.

          We must be careful how we use ++ in a loop. First is how not to use it:

          DO NOT

          naive_reverse([H|T]) ->
          │ │ │ +    naive_reverse(T) ++ [H];
          │ │ │ +naive_reverse([]) ->
          │ │ │ +    [].

          As the ++ operator copies its left-hand side operand, the growing │ │ │ +result is copied repeatedly, leading to quadratic complexity.

          On the other hand, using ++ in loop like this is perfectly fine:

          OK

          naive_but_ok_reverse(List) ->
          │ │ │ +    naive_but_ok_reverse(List, []).
          │ │ │  
          │ │ │ -naive_but_ok_reverse([H|T], Acc) ->
          │ │ │ -    naive_but_ok_reverse(T, [H] ++ Acc);
          │ │ │ -naive_but_ok_reverse([], Acc) ->
          │ │ │ +naive_but_ok_reverse([H|T], Acc) ->
          │ │ │ +    naive_but_ok_reverse(T, [H] ++ Acc);
          │ │ │ +naive_but_ok_reverse([], Acc) ->
          │ │ │      Acc.

          Each list element is copied only once. The growing result Acc is the right-hand │ │ │ -side operand, which it is not copied.

          Experienced Erlang programmers would probably write as follows:

          DO

          vanilla_reverse([H|T], Acc) ->
          │ │ │ -    vanilla_reverse(T, [H|Acc]);
          │ │ │ -vanilla_reverse([], Acc) ->
          │ │ │ +side operand, which it is not copied.

          Experienced Erlang programmers would probably write as follows:

          DO

          vanilla_reverse([H|T], Acc) ->
          │ │ │ +    vanilla_reverse(T, [H|Acc]);
          │ │ │ +vanilla_reverse([], Acc) ->
          │ │ │      Acc.

          In principle, this is slightly more efficient because the list element [H] │ │ │ is not built before being copied and discarded. In practice, the compiler │ │ │ rewrites [H] ++ Acc to [H|Acc].

          │ │ │ │ │ │ │ │ │ │ │ │ Timer Module │ │ │ @@ -160,77 +160,77 @@ │ │ │ therefore harmless.

          │ │ │ │ │ │ │ │ │ │ │ │ Accidental Copying and Loss of Sharing │ │ │

          │ │ │

          When spawning a new process using a fun, one can accidentally copy more data to │ │ │ -the process than intended. For example:

          DO NOT

          accidental1(State) ->
          │ │ │ -    spawn(fun() ->
          │ │ │ -                  io:format("~p\n", [State#state.info])
          │ │ │ -          end).

          The code in the fun will extract one element from the record and print it. The │ │ │ +the process than intended. For example:

          DO NOT

          accidental1(State) ->
          │ │ │ +    spawn(fun() ->
          │ │ │ +                  io:format("~p\n", [State#state.info])
          │ │ │ +          end).

          The code in the fun will extract one element from the record and print it. The │ │ │ rest of the state record is not used. However, when the spawn/1 │ │ │ -function is executed, the entire record is copied to the newly created process.

          The same kind of problem can happen with a map:

          DO NOT

          accidental2(State) ->
          │ │ │ -    spawn(fun() ->
          │ │ │ -                  io:format("~p\n", [map_get(info, State)])
          │ │ │ -          end).

          In the following example (part of a module implementing the gen_server │ │ │ -behavior) the created fun is sent to another process:

          DO NOT

          handle_call(give_me_a_fun, _From, State) ->
          │ │ │ -    Fun = fun() -> State#state.size =:= 42 end,
          │ │ │ -    {reply, Fun, State}.

          How bad that unnecessary copy is depends on the contents of the record or the │ │ │ -map.

          For example, if the state record is initialized like this:

          init1() ->
          │ │ │ -    #state{data=lists:seq(1, 10000)}.

          a list with 10000 elements (or about 20000 heap words) will be copied to the │ │ │ +function is executed, the entire record is copied to the newly created process.

          The same kind of problem can happen with a map:

          DO NOT

          accidental2(State) ->
          │ │ │ +    spawn(fun() ->
          │ │ │ +                  io:format("~p\n", [map_get(info, State)])
          │ │ │ +          end).

          In the following example (part of a module implementing the gen_server │ │ │ +behavior) the created fun is sent to another process:

          DO NOT

          handle_call(give_me_a_fun, _From, State) ->
          │ │ │ +    Fun = fun() -> State#state.size =:= 42 end,
          │ │ │ +    {reply, Fun, State}.

          How bad that unnecessary copy is depends on the contents of the record or the │ │ │ +map.

          For example, if the state record is initialized like this:

          init1() ->
          │ │ │ +    #state{data=lists:seq(1, 10000)}.

          a list with 10000 elements (or about 20000 heap words) will be copied to the │ │ │ newly created process.

          An unnecessary copy of 10000 element list can be bad enough, but it can get even │ │ │ worse if the state record contains shared subterms. Here is a simple example │ │ │ -of a term with a shared subterm:

          {SubTerm, SubTerm}

          When a term is copied to another process, sharing of subterms will be lost and │ │ │ -the copied term can be many times larger than the original term. For example:

          init2() ->
          │ │ │ -    SharedSubTerms = lists:foldl(fun(_, A) -> [A|A] end, [0], lists:seq(1, 15)),
          │ │ │ -    #state{data=Shared}.

          In the process that calls init2/0, the size of the data field in the state │ │ │ +of a term with a shared subterm:

          {SubTerm, SubTerm}

          When a term is copied to another process, sharing of subterms will be lost and │ │ │ +the copied term can be many times larger than the original term. For example:

          init2() ->
          │ │ │ +    SharedSubTerms = lists:foldl(fun(_, A) -> [A|A] end, [0], lists:seq(1, 15)),
          │ │ │ +    #state{data=Shared}.

          In the process that calls init2/0, the size of the data field in the state │ │ │ record will be 32 heap words. When the record is copied to the newly created │ │ │ process, sharing will be lost and the size of the copied data field will be │ │ │ 131070 heap words. More details about │ │ │ loss off sharing are found in a later │ │ │ section.

          To avoid the problem, outside of the fun extract only the fields of the record │ │ │ -that are actually used:

          DO

          fixed_accidental1(State) ->
          │ │ │ +that are actually used:

          DO

          fixed_accidental1(State) ->
          │ │ │      Info = State#state.info,
          │ │ │ -    spawn(fun() ->
          │ │ │ -                  io:format("~p\n", [Info])
          │ │ │ -          end).

          Similarly, outside of the fun extract only the map elements that are actually │ │ │ -used:

          DO

          fixed_accidental2(State) ->
          │ │ │ -    Info = map_get(info, State),
          │ │ │ -    spawn(fun() ->
          │ │ │ -                  io:format("~p\n", [Info])
          │ │ │ -          end).

          │ │ │ + spawn(fun() -> │ │ │ + io:format("~p\n", [Info]) │ │ │ + end).

          Similarly, outside of the fun extract only the map elements that are actually │ │ │ +used:

          DO

          fixed_accidental2(State) ->
          │ │ │ +    Info = map_get(info, State),
          │ │ │ +    spawn(fun() ->
          │ │ │ +                  io:format("~p\n", [Info])
          │ │ │ +          end).

          │ │ │ │ │ │ │ │ │ │ │ │ list_to_atom/1 │ │ │

          │ │ │

          Atoms are not garbage-collected. Once an atom is created, it is never removed. │ │ │ The emulator terminates if the limit for the number of atoms (1,048,576 by │ │ │ default) is reached.

          Therefore, converting arbitrary input strings to atoms can be dangerous in a │ │ │ system that runs continuously. If only certain well-defined atoms are allowed as │ │ │ input, list_to_existing_atom/1 or │ │ │ binary_to_existing_atom/1 can be used │ │ │ to guard against a denial-of-service attack. (All atoms that are allowed must │ │ │ have been created earlier, for example, by using all of them in a module │ │ │ and loading that module.)

          Using list_to_atom/1 to construct an atom that │ │ │ -is passed to apply/3 is quite expensive.

          DO NOT

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

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

          DO NOT

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

          │ │ │ │ │ │ │ │ │ │ │ │ length/1 │ │ │

          │ │ │

          The time for calculating the length of a list is proportional to the length of │ │ │ the list, as opposed to tuple_size/1, │ │ │ byte_size/1, and bit_size/1, which all │ │ │ execute in constant time.

          Normally, there is no need to worry about the speed of length/1, │ │ │ because it is efficiently implemented in C. In time-critical code, you might │ │ │ want to avoid it if the input list could potentially be very long.

          Some uses of length/1 can be replaced by matching. For example, │ │ │ -the following code:

          foo(L) when length(L) >= 3 ->
          │ │ │ -    ...

          can be rewritten to:

          foo([_,_,_|_]=L) ->
          │ │ │ +the following code:

          foo(L) when length(L) >= 3 ->
          │ │ │ +    ...

          can be rewritten to:

          foo([_,_,_|_]=L) ->
          │ │ │     ...

          One slight difference is that length(L) fails if L is an │ │ │ improper list, while the pattern in the second code fragment accepts an improper │ │ │ list.

          │ │ │ │ │ │ │ │ │ │ │ │ setelement/3 │ │ │ @@ -238,18 +238,18 @@ │ │ │

          setelement/3 copies the tuple it modifies. Therefore, │ │ │ updating a tuple in a loop using setelement/3 creates a new │ │ │ copy of the tuple every time.

          There is one exception to the rule that the tuple is copied. If the compiler │ │ │ clearly can see that destructively updating the tuple would give the same result │ │ │ as if the tuple was copied, the call to setelement/3 is │ │ │ replaced with a special destructive setelement instruction. In the following │ │ │ code sequence, the first setelement/3 call copies the tuple │ │ │ -and modifies the ninth element:

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

          The two following setelement/3 calls modify the tuple in │ │ │ +and modifies the ninth element:

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

          The two following setelement/3 calls modify the tuple in │ │ │ place.

          For the optimization to be applied, all the following conditions must be true:

          • The tuple argument must be known to be a tuple of a known size.
          • The indices must be integer literals, not variables or expressions.
          • The indices must be given in descending order.
          • There must be no calls to another function in between the calls to │ │ │ setelement/3.
          • The tuple returned from one setelement/3 call must only be │ │ │ used in the subsequent call to setelement/3.

          If the code cannot be structured as in the multiple_setelement/1 example, the │ │ │ best way to modify multiple elements in a large tuple is to convert the tuple to │ │ │ a list, modify the list, and convert it back to a tuple.

          │ │ │ │ │ │ │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/conc_prog.html │ │ │ @@ -132,107 +132,107 @@ │ │ │ threads of execution in an Erlang program and to allow these threads to │ │ │ communicate with each other. In Erlang, each thread of execution is called a │ │ │ process.

          (Aside: the term "process" is usually used when the threads of execution share │ │ │ no data with each other and the term "thread" when they share data in some way. │ │ │ Threads of execution in Erlang share no data, that is why they are called │ │ │ processes).

          The Erlang BIF spawn is used to create a new process: │ │ │ spawn(Module, Exported_Function, List of Arguments). Consider the following │ │ │ -module:

          -module(tut14).
          │ │ │ +module:

          -module(tut14).
          │ │ │  
          │ │ │ --export([start/0, say_something/2]).
          │ │ │ +-export([start/0, say_something/2]).
          │ │ │  
          │ │ │ -say_something(What, 0) ->
          │ │ │ +say_something(What, 0) ->
          │ │ │      done;
          │ │ │ -say_something(What, Times) ->
          │ │ │ -    io:format("~p~n", [What]),
          │ │ │ -    say_something(What, Times - 1).
          │ │ │ -
          │ │ │ -start() ->
          │ │ │ -    spawn(tut14, say_something, [hello, 3]),
          │ │ │ -    spawn(tut14, say_something, [goodbye, 3]).
          5> c(tut14).
          │ │ │ -{ok,tut14}
          │ │ │ -6> tut14:say_something(hello, 3).
          │ │ │ +say_something(What, Times) ->
          │ │ │ +    io:format("~p~n", [What]),
          │ │ │ +    say_something(What, Times - 1).
          │ │ │ +
          │ │ │ +start() ->
          │ │ │ +    spawn(tut14, say_something, [hello, 3]),
          │ │ │ +    spawn(tut14, say_something, [goodbye, 3]).
          5> c(tut14).
          │ │ │ +{ok,tut14}
          │ │ │ +6> tut14:say_something(hello, 3).
          │ │ │  hello
          │ │ │  hello
          │ │ │  hello
          │ │ │  done

          As shown, the function say_something writes its first argument the number of │ │ │ times specified by second argument. The function start starts two Erlang │ │ │ processes, one that writes "hello" three times and one that writes "goodbye" │ │ │ three times. Both processes use the function say_something. Notice that a │ │ │ function used in this way by spawn, to start a process, must be exported from │ │ │ -the module (that is, in the -export at the start of the module).

          9> tut14:start().
          │ │ │ +the module (that is, in the -export at the start of the module).

          9> tut14:start().
          │ │ │  hello
          │ │ │  goodbye
          │ │ │  <0.63.0>
          │ │ │  hello
          │ │ │  goodbye
          │ │ │  hello
          │ │ │  goodbye

          Notice that it did not write "hello" three times and then "goodbye" three times. │ │ │ Instead, the first process wrote a "hello", the second a "goodbye", the first │ │ │ another "hello" and so forth. But where did the <0.63.0> come from? The return │ │ │ value of a function is the return value of the last "thing" in the function. The │ │ │ -last thing in the function start is

          spawn(tut14, say_something, [goodbye, 3]).

          spawn returns a process identifier, or pid, which uniquely identifies the │ │ │ +last thing in the function start is

          spawn(tut14, say_something, [goodbye, 3]).

          spawn returns a process identifier, or pid, which uniquely identifies the │ │ │ process. So <0.63.0> is the pid of the spawn function call above. The next │ │ │ example shows how to use pids.

          Notice also that ~p is used instead of ~w in io:format/2. To quote the manual:

          ~p Writes the data with standard syntax in the same way as ~w, but breaks terms │ │ │ whose printed representation is longer than one line into many lines and indents │ │ │ each line sensibly. It also tries to detect flat lists of printable characters and │ │ │ to output these as strings

          │ │ │ │ │ │ │ │ │ │ │ │ Message Passing │ │ │

          │ │ │

          In the following example two processes are created and they send messages to │ │ │ -each other a number of times.

          -module(tut15).
          │ │ │ +each other a number of times.

          -module(tut15).
          │ │ │  
          │ │ │ --export([start/0, ping/2, pong/0]).
          │ │ │ +-export([start/0, ping/2, pong/0]).
          │ │ │  
          │ │ │ -ping(0, Pong_PID) ->
          │ │ │ +ping(0, Pong_PID) ->
          │ │ │      Pong_PID ! finished,
          │ │ │ -    io:format("ping finished~n", []);
          │ │ │ +    io:format("ping finished~n", []);
          │ │ │  
          │ │ │ -ping(N, Pong_PID) ->
          │ │ │ -    Pong_PID ! {ping, self()},
          │ │ │ +ping(N, Pong_PID) ->
          │ │ │ +    Pong_PID ! {ping, self()},
          │ │ │      receive
          │ │ │          pong ->
          │ │ │ -            io:format("Ping received pong~n", [])
          │ │ │ +            io:format("Ping received pong~n", [])
          │ │ │      end,
          │ │ │ -    ping(N - 1, Pong_PID).
          │ │ │ +    ping(N - 1, Pong_PID).
          │ │ │  
          │ │ │ -pong() ->
          │ │ │ +pong() ->
          │ │ │      receive
          │ │ │          finished ->
          │ │ │ -            io:format("Pong finished~n", []);
          │ │ │ -        {ping, Ping_PID} ->
          │ │ │ -            io:format("Pong received ping~n", []),
          │ │ │ +            io:format("Pong finished~n", []);
          │ │ │ +        {ping, Ping_PID} ->
          │ │ │ +            io:format("Pong received ping~n", []),
          │ │ │              Ping_PID ! pong,
          │ │ │ -            pong()
          │ │ │ +            pong()
          │ │ │      end.
          │ │ │  
          │ │ │ -start() ->
          │ │ │ -    Pong_PID = spawn(tut15, pong, []),
          │ │ │ -    spawn(tut15, ping, [3, Pong_PID]).
          1> c(tut15).
          │ │ │ -{ok,tut15}
          │ │ │ -2> tut15: start().
          │ │ │ +start() ->
          │ │ │ +    Pong_PID = spawn(tut15, pong, []),
          │ │ │ +    spawn(tut15, ping, [3, Pong_PID]).
          1> c(tut15).
          │ │ │ +{ok,tut15}
          │ │ │ +2> tut15: start().
          │ │ │  <0.36.0>
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  ping finished
          │ │ │ -Pong finished

          The function start first creates a process, let us call it "pong":

          Pong_PID = spawn(tut15, pong, [])

          This process executes tut15:pong(). Pong_PID is the process identity of the │ │ │ -"pong" process. The function start now creates another process "ping":

          spawn(tut15, ping, [3, Pong_PID]),

          This process executes:

          tut15:ping(3, Pong_PID)

          <0.36.0> is the return value from the start function.

          The process "pong" now does:

          receive
          │ │ │ +Pong finished

          The function start first creates a process, let us call it "pong":

          Pong_PID = spawn(tut15, pong, [])

          This process executes tut15:pong(). Pong_PID is the process identity of the │ │ │ +"pong" process. The function start now creates another process "ping":

          spawn(tut15, ping, [3, Pong_PID]),

          This process executes:

          tut15:ping(3, Pong_PID)

          <0.36.0> is the return value from the start function.

          The process "pong" now does:

          receive
          │ │ │      finished ->
          │ │ │ -        io:format("Pong finished~n", []);
          │ │ │ -    {ping, Ping_PID} ->
          │ │ │ -        io:format("Pong received ping~n", []),
          │ │ │ +        io:format("Pong finished~n", []);
          │ │ │ +    {ping, Ping_PID} ->
          │ │ │ +        io:format("Pong received ping~n", []),
          │ │ │          Ping_PID ! pong,
          │ │ │ -        pong()
          │ │ │ +        pong()
          │ │ │  end.

          The receive construct is used to allow processes to wait for messages from │ │ │ other processes. It has the following format:

          receive
          │ │ │     pattern1 ->
          │ │ │         actions1;
          │ │ │     pattern2 ->
          │ │ │         actions2;
          │ │ │     ....
          │ │ │ @@ -253,84 +253,84 @@
          │ │ │  queue (keeping the first message and any other messages in the queue). If the
          │ │ │  second message does not match, the third message is tried, and so on, until the
          │ │ │  end of the queue is reached. If the end of the queue is reached, the process
          │ │ │  blocks (stops execution) and waits until a new message is received and this
          │ │ │  procedure is repeated.

          The Erlang implementation is "clever" and minimizes the number of times each │ │ │ message is tested against the patterns in each receive.

          Now back to the ping pong example.

          "Pong" is waiting for messages. If the atom finished is received, "pong" │ │ │ writes "Pong finished" to the output and, as it has nothing more to do, │ │ │ -terminates. If it receives a message with the format:

          {ping, Ping_PID}

          it writes "Pong received ping" to the output and sends the atom pong to the │ │ │ +terminates. If it receives a message with the format:

          {ping, Ping_PID}

          it writes "Pong received ping" to the output and sends the atom pong to the │ │ │ process "ping":

          Ping_PID ! pong

          Notice how the operator "!" is used to send messages. The syntax of "!" is:

          Pid ! Message

          That is, Message (any Erlang term) is sent to the process with identity Pid.

          After sending the message pong to the process "ping", "pong" calls the pong │ │ │ function again, which causes it to get back to the receive again and wait for │ │ │ -another message.

          Now let us look at the process "ping". Recall that it was started by executing:

          tut15:ping(3, Pong_PID)

          Looking at the function ping/2, the second clause of ping/2 is executed │ │ │ +another message.

          Now let us look at the process "ping". Recall that it was started by executing:

          tut15:ping(3, Pong_PID)

          Looking at the function ping/2, the second clause of ping/2 is executed │ │ │ since the value of the first argument is 3 (not 0) (first clause head is │ │ │ -ping(0,Pong_PID), second clause head is ping(N,Pong_PID), so N becomes 3).

          The second clause sends a message to "pong":

          Pong_PID ! {ping, self()},

          self/0 returns the pid of the process that executes self/0, in this case the │ │ │ +ping(0,Pong_PID), second clause head is ping(N,Pong_PID), so N becomes 3).

          The second clause sends a message to "pong":

          Pong_PID ! {ping, self()},

          self/0 returns the pid of the process that executes self/0, in this case the │ │ │ pid of "ping". (Recall the code for "pong", this lands up in the variable │ │ │ Ping_PID in the receive previously explained.)

          "Ping" now waits for a reply from "pong":

          receive
          │ │ │      pong ->
          │ │ │ -        io:format("Ping received pong~n", [])
          │ │ │ +        io:format("Ping received pong~n", [])
          │ │ │  end,

          It writes "Ping received pong" when this reply arrives, after which "ping" calls │ │ │ -the ping function again.

          ping(N - 1, Pong_PID)

          N-1 causes the first argument to be decremented until it becomes 0. When this │ │ │ -occurs, the first clause of ping/2 is executed:

          ping(0, Pong_PID) ->
          │ │ │ +the ping function again.

          ping(N - 1, Pong_PID)

          N-1 causes the first argument to be decremented until it becomes 0. When this │ │ │ +occurs, the first clause of ping/2 is executed:

          ping(0, Pong_PID) ->
          │ │ │      Pong_PID !  finished,
          │ │ │ -    io:format("ping finished~n", []);

          The atom finished is sent to "pong" (causing it to terminate as described │ │ │ + io:format("ping finished~n", []);

          The atom finished is sent to "pong" (causing it to terminate as described │ │ │ above) and "ping finished" is written to the output. "Ping" then terminates as │ │ │ it has nothing left to do.

          │ │ │ │ │ │ │ │ │ │ │ │ Registered Process Names │ │ │

          │ │ │

          In the above example, "pong" was first created to be able to give the identity │ │ │ of "pong" when "ping" was started. That is, in some way "ping" must be able to │ │ │ know the identity of "pong" to be able to send a message to it. Sometimes │ │ │ processes which need to know each other's identities are started independently │ │ │ of each other. Erlang thus provides a mechanism for processes to be given names │ │ │ so that these names can be used as identities instead of pids. This is done by │ │ │ -using the register BIF:

          register(some_atom, Pid)

          Let us now rewrite the ping pong example using this and give the name pong to │ │ │ -the "pong" process:

          -module(tut16).
          │ │ │ +using the register BIF:

          register(some_atom, Pid)

          Let us now rewrite the ping pong example using this and give the name pong to │ │ │ +the "pong" process:

          -module(tut16).
          │ │ │  
          │ │ │ --export([start/0, ping/1, pong/0]).
          │ │ │ +-export([start/0, ping/1, pong/0]).
          │ │ │  
          │ │ │ -ping(0) ->
          │ │ │ +ping(0) ->
          │ │ │      pong ! finished,
          │ │ │ -    io:format("ping finished~n", []);
          │ │ │ +    io:format("ping finished~n", []);
          │ │ │  
          │ │ │ -ping(N) ->
          │ │ │ -    pong ! {ping, self()},
          │ │ │ +ping(N) ->
          │ │ │ +    pong ! {ping, self()},
          │ │ │      receive
          │ │ │          pong ->
          │ │ │ -            io:format("Ping received pong~n", [])
          │ │ │ +            io:format("Ping received pong~n", [])
          │ │ │      end,
          │ │ │ -    ping(N - 1).
          │ │ │ +    ping(N - 1).
          │ │ │  
          │ │ │ -pong() ->
          │ │ │ +pong() ->
          │ │ │      receive
          │ │ │          finished ->
          │ │ │ -            io:format("Pong finished~n", []);
          │ │ │ -        {ping, Ping_PID} ->
          │ │ │ -            io:format("Pong received ping~n", []),
          │ │ │ +            io:format("Pong finished~n", []);
          │ │ │ +        {ping, Ping_PID} ->
          │ │ │ +            io:format("Pong received ping~n", []),
          │ │ │              Ping_PID ! pong,
          │ │ │ -            pong()
          │ │ │ +            pong()
          │ │ │      end.
          │ │ │  
          │ │ │ -start() ->
          │ │ │ -    register(pong, spawn(tut16, pong, [])),
          │ │ │ -    spawn(tut16, ping, [3]).
          2> c(tut16).
          │ │ │ -{ok, tut16}
          │ │ │ -3> tut16:start().
          │ │ │ +start() ->
          │ │ │ +    register(pong, spawn(tut16, pong, [])),
          │ │ │ +    spawn(tut16, ping, [3]).
          2> c(tut16).
          │ │ │ +{ok, tut16}
          │ │ │ +3> tut16:start().
          │ │ │  <0.38.0>
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  ping finished
          │ │ │ -Pong finished

          Here the start/0 function,

          register(pong, spawn(tut16, pong, [])),

          both spawns the "pong" process and gives it the name pong. In the "ping" │ │ │ -process, messages can be sent to pong by:

          pong ! {ping, self()},

          ping/2 now becomes ping/1 as the argument Pong_PID is not needed.

          │ │ │ +Pong finished

          Here the start/0 function,

          register(pong, spawn(tut16, pong, [])),

          both spawns the "pong" process and gives it the name pong. In the "ping" │ │ │ +process, messages can be sent to pong by:

          pong ! {ping, self()},

          ping/2 now becomes ping/1 as the argument Pong_PID is not needed.

          │ │ │ │ │ │ │ │ │ │ │ │ Distributed Programming │ │ │

          │ │ │

          Let us rewrite the ping pong program with "ping" and "pong" on different │ │ │ computers. First a few things are needed to set up to get this to work. The │ │ │ @@ -350,106 +350,106 @@ │ │ │ of the file. This is a requirement.

          When you start an Erlang system that is going to talk to other Erlang systems, │ │ │ you must give it a name, for example:

          $ erl -sname my_name

          We will see more details of this later. If you want to experiment with │ │ │ distributed Erlang, but you only have one computer to work on, you can start two │ │ │ separate Erlang systems on the same computer but give them different names. Each │ │ │ Erlang system running on a computer is called an Erlang node.

          (Note: erl -sname assumes that all nodes are in the same IP domain and we can │ │ │ use only the first component of the IP address, if we want to use nodes in │ │ │ different domains we use -name instead, but then all IP address must be given │ │ │ -in full.)

          Here is the ping pong example modified to run on two separate nodes:

          -module(tut17).
          │ │ │ +in full.)

          Here is the ping pong example modified to run on two separate nodes:

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

          Let us assume there are two computers called gollum and kosken. First a node is │ │ │ +start_ping(Pong_Node) -> │ │ │ + spawn(tut17, ping, [3, Pong_Node]).

          Let us assume there are two computers called gollum and kosken. First a node is │ │ │ started on kosken, called ping, and then a node on gollum, called pong.

          On kosken (on a Linux/UNIX system):

          kosken> erl -sname ping
          │ │ │  Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0]
          │ │ │  
          │ │ │  Eshell V5.2.3.7  (abort with ^G)
          │ │ │  (ping@kosken)1>

          On gollum:

          gollum> erl -sname pong
          │ │ │  Erlang (BEAM) emulator version 5.2.3.7 [hipe] [threads:0]
          │ │ │  
          │ │ │  Eshell V5.2.3.7  (abort with ^G)
          │ │ │ -(pong@gollum)1>

          Now the "pong" process on gollum is started:

          (pong@gollum)1> tut17:start_pong().
          │ │ │ +(pong@gollum)1>

          Now the "pong" process on gollum is started:

          (pong@gollum)1> tut17:start_pong().
          │ │ │  true

          And the "ping" process on kosken is started (from the code above you can see │ │ │ that a parameter of the start_ping function is the node name of the Erlang │ │ │ -system where "pong" is running):

          (ping@kosken)1> tut17:start_ping(pong@gollum).
          │ │ │ +system where "pong" is running):

          (ping@kosken)1> tut17:start_ping(pong@gollum).
          │ │ │  <0.37.0>
          │ │ │  Ping received pong
          │ │ │  Ping received pong
          │ │ │  Ping received pong
          │ │ │  ping finished

          As shown, the ping pong program has run. On the "pong" side:

          (pong@gollum)2> 
          │ │ │  Pong received ping
          │ │ │  Pong received ping
          │ │ │  Pong received ping
          │ │ │  Pong finished
          │ │ │ -(pong@gollum)2> 

          Looking at the tut17 code, you see that the pong function itself is │ │ │ +(pong@gollum)2>

          Looking at the tut17 code, you see that the pong function itself is │ │ │ unchanged, the following lines work in the same way irrespective of on which │ │ │ -node the "ping" process is executes:

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

          {ping, Ping_PID} ->
          │ │ │ +    io:format("Pong received ping~n", []),
          │ │ │      Ping_PID ! pong,

          Thus, Erlang pids contain information about where the process executes. So if │ │ │ you know the pid of a process, the ! operator can be used to send it a │ │ │ -message disregarding if the process is on the same node or on a different node.

          A difference is how messages are sent to a registered process on another node:

          {pong, Pong_Node} ! {ping, self()},

          A tuple {registered_name,node_name} is used instead of just the │ │ │ +message disregarding if the process is on the same node or on a different node.

          A difference is how messages are sent to a registered process on another node:

          {pong, Pong_Node} ! {ping, self()},

          A tuple {registered_name,node_name} is used instead of just the │ │ │ registered_name.

          In the previous example, "ping" and "pong" were started from the shells of two │ │ │ separate Erlang nodes. spawn can also be used to start processes in other │ │ │ nodes.

          The next example is the ping pong program, yet again, but this time "ping" is │ │ │ -started in another node:

          -module(tut18).
          │ │ │ +started in another node:

          -module(tut18).
          │ │ │  
          │ │ │ --export([start/1,  ping/2, pong/0]).
          │ │ │ +-export([start/1,  ping/2, pong/0]).
          │ │ │  
          │ │ │ -ping(0, Pong_Node) ->
          │ │ │ -    {pong, Pong_Node} ! finished,
          │ │ │ -    io:format("ping finished~n", []);
          │ │ │ +ping(0, Pong_Node) ->
          │ │ │ +    {pong, Pong_Node} ! finished,
          │ │ │ +    io:format("ping finished~n", []);
          │ │ │  
          │ │ │ -ping(N, Pong_Node) ->
          │ │ │ -    {pong, Pong_Node} ! {ping, self()},
          │ │ │ +ping(N, Pong_Node) ->
          │ │ │ +    {pong, Pong_Node} ! {ping, self()},
          │ │ │      receive
          │ │ │          pong ->
          │ │ │ -            io:format("Ping received pong~n", [])
          │ │ │ +            io:format("Ping received pong~n", [])
          │ │ │      end,
          │ │ │ -    ping(N - 1, Pong_Node).
          │ │ │ +    ping(N - 1, Pong_Node).
          │ │ │  
          │ │ │ -pong() ->
          │ │ │ +pong() ->
          │ │ │      receive
          │ │ │          finished ->
          │ │ │ -            io:format("Pong finished~n", []);
          │ │ │ -        {ping, Ping_PID} ->
          │ │ │ -            io:format("Pong received ping~n", []),
          │ │ │ +            io:format("Pong finished~n", []);
          │ │ │ +        {ping, Ping_PID} ->
          │ │ │ +            io:format("Pong received ping~n", []),
          │ │ │              Ping_PID ! pong,
          │ │ │ -            pong()
          │ │ │ +            pong()
          │ │ │      end.
          │ │ │  
          │ │ │ -start(Ping_Node) ->
          │ │ │ -    register(pong, spawn(tut18, pong, [])),
          │ │ │ -    spawn(Ping_Node, tut18, ping, [3, node()]).

          Assuming an Erlang system called ping (but not the "ping" process) has already │ │ │ -been started on kosken, then on gollum this is done:

          (pong@gollum)1> tut18:start(ping@kosken).
          │ │ │ +start(Ping_Node) ->
          │ │ │ +    register(pong, spawn(tut18, pong, [])),
          │ │ │ +    spawn(Ping_Node, tut18, ping, [3, node()]).

          Assuming an Erlang system called ping (but not the "ping" process) has already │ │ │ +been started on kosken, then on gollum this is done:

          (pong@gollum)1> tut18:start(ping@kosken).
          │ │ │  <3934.39.0>
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │  Pong received ping
          │ │ │  Ping received pong
          │ │ │ @@ -516,188 +516,188 @@
          │ │ │  %%% Started: messenger:client(Server_Node, Name)
          │ │ │  %%% To client: logoff
          │ │ │  %%% To client: {message_to, ToName, Message}
          │ │ │  %%%
          │ │ │  %%% Configuration: change the server_node() function to return the
          │ │ │  %%% name of the node where the messenger server runs
          │ │ │  
          │ │ │ --module(messenger).
          │ │ │ --export([start_server/0, server/1, logon/1, logoff/0, message/2, client/2]).
          │ │ │ +-module(messenger).
          │ │ │ +-export([start_server/0, server/1, logon/1, logoff/0, message/2, client/2]).
          │ │ │  
          │ │ │  %%% Change the function below to return the name of the node where the
          │ │ │  %%% messenger server runs
          │ │ │ -server_node() ->
          │ │ │ +server_node() ->
          │ │ │      messenger@super.
          │ │ │  
          │ │ │  %%% This is the server process for the "messenger"
          │ │ │  %%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
          │ │ │ -server(User_List) ->
          │ │ │ +server(User_List) ->
          │ │ │      receive
          │ │ │ -        {From, logon, Name} ->
          │ │ │ -            New_User_List = server_logon(From, Name, User_List),
          │ │ │ -            server(New_User_List);
          │ │ │ -        {From, logoff} ->
          │ │ │ -            New_User_List = server_logoff(From, User_List),
          │ │ │ -            server(New_User_List);
          │ │ │ -        {From, message_to, To, Message} ->
          │ │ │ -            server_transfer(From, To, Message, User_List),
          │ │ │ -            io:format("list is now: ~p~n", [User_List]),
          │ │ │ -            server(User_List)
          │ │ │ +        {From, logon, Name} ->
          │ │ │ +            New_User_List = server_logon(From, Name, User_List),
          │ │ │ +            server(New_User_List);
          │ │ │ +        {From, logoff} ->
          │ │ │ +            New_User_List = server_logoff(From, User_List),
          │ │ │ +            server(New_User_List);
          │ │ │ +        {From, message_to, To, Message} ->
          │ │ │ +            server_transfer(From, To, Message, User_List),
          │ │ │ +            io:format("list is now: ~p~n", [User_List]),
          │ │ │ +            server(User_List)
          │ │ │      end.
          │ │ │  
          │ │ │  %%% Start the server
          │ │ │ -start_server() ->
          │ │ │ -    register(messenger, spawn(messenger, server, [[]])).
          │ │ │ +start_server() ->
          │ │ │ +    register(messenger, spawn(messenger, server, [[]])).
          │ │ │  
          │ │ │  
          │ │ │  %%% Server adds a new user to the user list
          │ │ │ -server_logon(From, Name, User_List) ->
          │ │ │ +server_logon(From, Name, User_List) ->
          │ │ │      %% check if logged on anywhere else
          │ │ │ -    case lists:keymember(Name, 2, User_List) of
          │ │ │ +    case lists:keymember(Name, 2, User_List) of
          │ │ │          true ->
          │ │ │ -            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
          │ │ │ +            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
          │ │ │              User_List;
          │ │ │          false ->
          │ │ │ -            From ! {messenger, logged_on},
          │ │ │ -            [{From, Name} | User_List]        %add user to the list
          │ │ │ +            From ! {messenger, logged_on},
          │ │ │ +            [{From, Name} | User_List]        %add user to the list
          │ │ │      end.
          │ │ │  
          │ │ │  %%% Server deletes a user from the user list
          │ │ │ -server_logoff(From, User_List) ->
          │ │ │ -    lists:keydelete(From, 1, User_List).
          │ │ │ +server_logoff(From, User_List) ->
          │ │ │ +    lists:keydelete(From, 1, User_List).
          │ │ │  
          │ │ │  
          │ │ │  %%% Server transfers a message between user
          │ │ │ -server_transfer(From, To, Message, User_List) ->
          │ │ │ +server_transfer(From, To, Message, User_List) ->
          │ │ │      %% check that the user is logged on and who he is
          │ │ │ -    case lists:keysearch(From, 1, User_List) of
          │ │ │ +    case lists:keysearch(From, 1, User_List) of
          │ │ │          false ->
          │ │ │ -            From ! {messenger, stop, you_are_not_logged_on};
          │ │ │ -        {value, {From, Name}} ->
          │ │ │ -            server_transfer(From, Name, To, Message, User_List)
          │ │ │ +            From ! {messenger, stop, you_are_not_logged_on};
          │ │ │ +        {value, {From, Name}} ->
          │ │ │ +            server_transfer(From, Name, To, Message, User_List)
          │ │ │      end.
          │ │ │  %%% If the user exists, send the message
          │ │ │ -server_transfer(From, Name, To, Message, User_List) ->
          │ │ │ +server_transfer(From, Name, To, Message, User_List) ->
          │ │ │      %% Find the receiver and send the message
          │ │ │ -    case lists:keysearch(To, 2, User_List) of
          │ │ │ +    case lists:keysearch(To, 2, User_List) of
          │ │ │          false ->
          │ │ │ -            From ! {messenger, receiver_not_found};
          │ │ │ -        {value, {ToPid, To}} ->
          │ │ │ -            ToPid ! {message_from, Name, Message},
          │ │ │ -            From ! {messenger, sent}
          │ │ │ +            From ! {messenger, receiver_not_found};
          │ │ │ +        {value, {ToPid, To}} ->
          │ │ │ +            ToPid ! {message_from, Name, Message},
          │ │ │ +            From ! {messenger, sent}
          │ │ │      end.
          │ │ │  
          │ │ │  
          │ │ │  %%% User Commands
          │ │ │ -logon(Name) ->
          │ │ │ -    case whereis(mess_client) of
          │ │ │ +logon(Name) ->
          │ │ │ +    case whereis(mess_client) of
          │ │ │          undefined ->
          │ │ │ -            register(mess_client,
          │ │ │ -                     spawn(messenger, client, [server_node(), Name]));
          │ │ │ +            register(mess_client,
          │ │ │ +                     spawn(messenger, client, [server_node(), Name]));
          │ │ │          _ -> already_logged_on
          │ │ │      end.
          │ │ │  
          │ │ │ -logoff() ->
          │ │ │ +logoff() ->
          │ │ │      mess_client ! logoff.
          │ │ │  
          │ │ │ -message(ToName, Message) ->
          │ │ │ -    case whereis(mess_client) of % Test if the client is running
          │ │ │ +message(ToName, Message) ->
          │ │ │ +    case whereis(mess_client) of % Test if the client is running
          │ │ │          undefined ->
          │ │ │              not_logged_on;
          │ │ │ -        _ -> mess_client ! {message_to, ToName, Message},
          │ │ │ +        _ -> mess_client ! {message_to, ToName, Message},
          │ │ │               ok
          │ │ │  end.
          │ │ │  
          │ │ │  
          │ │ │  %%% The client process which runs on each server node
          │ │ │ -client(Server_Node, Name) ->
          │ │ │ -    {messenger, Server_Node} ! {self(), logon, Name},
          │ │ │ -    await_result(),
          │ │ │ -    client(Server_Node).
          │ │ │ +client(Server_Node, Name) ->
          │ │ │ +    {messenger, Server_Node} ! {self(), logon, Name},
          │ │ │ +    await_result(),
          │ │ │ +    client(Server_Node).
          │ │ │  
          │ │ │ -client(Server_Node) ->
          │ │ │ +client(Server_Node) ->
          │ │ │      receive
          │ │ │          logoff ->
          │ │ │ -            {messenger, Server_Node} ! {self(), logoff},
          │ │ │ -            exit(normal);
          │ │ │ -        {message_to, ToName, Message} ->
          │ │ │ -            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
          │ │ │ -            await_result();
          │ │ │ -        {message_from, FromName, Message} ->
          │ │ │ -            io:format("Message from ~p: ~p~n", [FromName, Message])
          │ │ │ +            {messenger, Server_Node} ! {self(), logoff},
          │ │ │ +            exit(normal);
          │ │ │ +        {message_to, ToName, Message} ->
          │ │ │ +            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
          │ │ │ +            await_result();
          │ │ │ +        {message_from, FromName, Message} ->
          │ │ │ +            io:format("Message from ~p: ~p~n", [FromName, Message])
          │ │ │      end,
          │ │ │ -    client(Server_Node).
          │ │ │ +    client(Server_Node).
          │ │ │  
          │ │ │  %%% wait for a response from the server
          │ │ │ -await_result() ->
          │ │ │ +await_result() ->
          │ │ │      receive
          │ │ │ -        {messenger, stop, Why} -> % Stop the client
          │ │ │ -            io:format("~p~n", [Why]),
          │ │ │ -            exit(normal);
          │ │ │ -        {messenger, What} ->  % Normal response
          │ │ │ -            io:format("~p~n", [What])
          │ │ │ +        {messenger, stop, Why} -> % Stop the client
          │ │ │ +            io:format("~p~n", [Why]),
          │ │ │ +            exit(normal);
          │ │ │ +        {messenger, What} ->  % Normal response
          │ │ │ +            io:format("~p~n", [What])
          │ │ │      end.

          To use this program, you need to:

          • Configure the server_node() function.
          • Copy the compiled code (messenger.beam) to the directory on each computer │ │ │ where you start Erlang.

          In the following example using this program, nodes are started on four different │ │ │ computers. If you do not have that many machines available on your network, you │ │ │ can start several nodes on the same machine.

          Four Erlang nodes are started up: messenger@super, c1@bilbo, c2@kosken, │ │ │ -c3@gollum.

          First the server at messenger@super is started up:

          (messenger@super)1> messenger:start_server().
          │ │ │ -true

          Now Peter logs on at c1@bilbo:

          (c1@bilbo)1> messenger:logon(peter).
          │ │ │ +c3@gollum.

          First the server at messenger@super is started up:

          (messenger@super)1> messenger:start_server().
          │ │ │ +true

          Now Peter logs on at c1@bilbo:

          (c1@bilbo)1> messenger:logon(peter).
          │ │ │  true
          │ │ │ -logged_on

          James logs on at c2@kosken:

          (c2@kosken)1> messenger:logon(james).
          │ │ │ +logged_on

          James logs on at c2@kosken:

          (c2@kosken)1> messenger:logon(james).
          │ │ │  true
          │ │ │ -logged_on

          And Fred logs on at c3@gollum:

          (c3@gollum)1> messenger:logon(fred).
          │ │ │ +logged_on

          And Fred logs on at c3@gollum:

          (c3@gollum)1> messenger:logon(fred).
          │ │ │  true
          │ │ │ -logged_on

          Now Peter sends Fred a message:

          (c1@bilbo)2> messenger:message(fred, "hello").
          │ │ │ +logged_on

          Now Peter sends Fred a message:

          (c1@bilbo)2> messenger:message(fred, "hello").
          │ │ │  ok
          │ │ │  sent

          Fred receives the message and sends a message to Peter and logs off:

          Message from peter: "hello"
          │ │ │ -(c3@gollum)2> messenger:message(peter, "go away, I'm busy").
          │ │ │ +(c3@gollum)2> messenger:message(peter, "go away, I'm busy").
          │ │ │  ok
          │ │ │  sent
          │ │ │ -(c3@gollum)3> messenger:logoff().
          │ │ │ -logoff

          James now tries to send a message to Fred:

          (c2@kosken)2> messenger:message(fred, "peter doesn't like you").
          │ │ │ +(c3@gollum)3> messenger:logoff().
          │ │ │ +logoff

          James now tries to send a message to Fred:

          (c2@kosken)2> messenger:message(fred, "peter doesn't like you").
          │ │ │  ok
          │ │ │  receiver_not_found

          But this fails as Fred has already logged off.

          First let us look at some of the new concepts that have been introduced.

          There are two versions of the server_transfer function: one with four │ │ │ arguments (server_transfer/4) and one with five (server_transfer/5). These │ │ │ are regarded by Erlang as two separate functions.

          Notice how to write the server function so that it calls itself, through │ │ │ server(User_List), and thus creates a loop. The Erlang compiler is "clever" │ │ │ and optimizes the code so that this really is a sort of loop and not a proper │ │ │ function call. But this only works if there is no code after the call. │ │ │ Otherwise, the compiler expects the call to return and make a proper function │ │ │ call. This would result in the process getting bigger and bigger for every loop.

          Functions in the lists module are used. This is a very useful module and a │ │ │ study of the manual page is recommended (erl -man lists). │ │ │ lists:keymember(Key,Position,Lists) looks through a list of tuples and looks │ │ │ at Position in each tuple to see if it is the same as Key. The first element │ │ │ is position 1. If it finds a tuple where the element at Position is the same │ │ │ -as Key, it returns true, otherwise false.

          3> lists:keymember(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
          │ │ │ +as Key, it returns true, otherwise false.

          3> lists:keymember(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
          │ │ │  true
          │ │ │ -4> lists:keymember(p, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
          │ │ │ +4> lists:keymember(p, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
          │ │ │  false

          lists:keydelete works in the same way but deletes the first tuple found (if │ │ │ -any) and returns the remaining list:

          5> lists:keydelete(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
          │ │ │ -[{x,y,z},{b,b,b},{q,r,s}]

          lists:keysearch is like lists:keymember, but it returns │ │ │ +any) and returns the remaining list:

          5> lists:keydelete(a, 2, [{x,y,z},{b,b,b},{b,a,c},{q,r,s}]).
          │ │ │ +[{x,y,z},{b,b,b},{q,r,s}]

          lists:keysearch is like lists:keymember, but it returns │ │ │ {value,Tuple_Found} or the atom false.

          There are many very useful functions in the lists module.

          An Erlang process (conceptually) runs until it does a receive and there is no │ │ │ message which it wants to receive in the message queue. "conceptually" is used │ │ │ here because the Erlang system shares the CPU time between the active processes │ │ │ in the system.

          A process terminates when there is nothing more for it to do, that is, the last │ │ │ function it calls simply returns and does not call another function. Another way │ │ │ for a process to terminate is for it to call exit/1. The argument │ │ │ to exit/1 has a special meaning, which is discussed later. In this │ │ │ example, exit(normal) is done, which has the same effect as a │ │ │ process running out of functions to call.

          The BIF whereis(RegisteredName) checks if a registered process │ │ │ of name RegisteredName exists. If it exists, the pid of that process is │ │ │ returned. If it does not exist, the atom undefined is returned.

          You should by now be able to understand most of the code in the │ │ │ messenger-module. Let us study one case in detail: a message is sent from one │ │ │ -user to another.

          The first user "sends" the message in the example above by:

          messenger:message(fred, "hello")

          After testing that the client process exists:

          whereis(mess_client)

          And a message is sent to mess_client:

          mess_client ! {message_to, fred, "hello"}

          The client sends the message to the server by:

          {messenger, messenger@super} ! {self(), message_to, fred, "hello"},

          And waits for a reply from the server.

          The server receives this message and calls:

          server_transfer(From, fred, "hello", User_List),

          This checks that the pid From is in the User_List:

          lists:keysearch(From, 1, User_List)

          If keysearch returns the atom false, some error has occurred and the server │ │ │ -sends back the message:

          From ! {messenger, stop, you_are_not_logged_on}

          This is received by the client, which in turn does exit(normal) │ │ │ +user to another.

          The first user "sends" the message in the example above by:

          messenger:message(fred, "hello")

          After testing that the client process exists:

          whereis(mess_client)

          And a message is sent to mess_client:

          mess_client ! {message_to, fred, "hello"}

          The client sends the message to the server by:

          {messenger, messenger@super} ! {self(), message_to, fred, "hello"},

          And waits for a reply from the server.

          The server receives this message and calls:

          server_transfer(From, fred, "hello", User_List),

          This checks that the pid From is in the User_List:

          lists:keysearch(From, 1, User_List)

          If keysearch returns the atom false, some error has occurred and the server │ │ │ +sends back the message:

          From ! {messenger, stop, you_are_not_logged_on}

          This is received by the client, which in turn does exit(normal) │ │ │ and terminates. If keysearch returns {value,{From,Name}} it is certain that │ │ │ -the user is logged on and that his name (peter) is in variable Name.

          Let us now call:

          server_transfer(From, peter, fred, "hello", User_List)

          Notice that as this is server_transfer/5, it is not the same as the previous │ │ │ +the user is logged on and that his name (peter) is in variable Name.

          Let us now call:

          server_transfer(From, peter, fred, "hello", User_List)

          Notice that as this is server_transfer/5, it is not the same as the previous │ │ │ function server_transfer/4. Another keysearch is done on User_List to find │ │ │ -the pid of the client corresponding to fred:

          lists:keysearch(fred, 2, User_List)

          This time argument 2 is used, which is the second element in the tuple. If this │ │ │ +the pid of the client corresponding to fred:

          lists:keysearch(fred, 2, User_List)

          This time argument 2 is used, which is the second element in the tuple. If this │ │ │ returns the atom false, fred is not logged on and the following message is │ │ │ -sent:

          From ! {messenger, receiver_not_found};

          This is received by the client.

          If keysearch returns:

          {value, {ToPid, fred}}

          The following message is sent to fred's client:

          ToPid ! {message_from, peter, "hello"},

          The following message is sent to peter's client:

          From ! {messenger, sent}

          Fred's client receives the message and prints it:

          {message_from, peter, "hello"} ->
          │ │ │ -    io:format("Message from ~p: ~p~n", [peter, "hello"])

          Peter's client receives the message in the await_result function.

          │ │ │ +sent:

          From ! {messenger, receiver_not_found};

          This is received by the client.

          If keysearch returns:

          {value, {ToPid, fred}}

          The following message is sent to fred's client:

          ToPid ! {message_from, peter, "hello"},

          The following message is sent to peter's client:

          From ! {messenger, sent}

          Fred's client receives the message and prints it:

          {message_from, peter, "hello"} ->
          │ │ │ +    io:format("Message from ~p: ~p~n", [peter, "hello"])

          Peter's client receives the message in the await_result function.

          │ │ │

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

          rel(4) manual page in │ │ │ SASL), which specifies the ERTS version and lists all applications that are to │ │ │ be included in the new basic target system. An example is the following │ │ │ mysystem.rel file:

          %% mysystem.rel
          │ │ │ -{release,
          │ │ │ - {"MYSYSTEM", "FIRST"},
          │ │ │ - {erts, "5.10.4"},
          │ │ │ - [{kernel, "2.16.4"},
          │ │ │ -  {stdlib, "1.19.4"},
          │ │ │ -  {sasl, "2.3.4"},
          │ │ │ -  {pea, "1.0"}]}.

          The listed applications are not only original Erlang/OTP applications but │ │ │ +{release, │ │ │ + {"MYSYSTEM", "FIRST"}, │ │ │ + {erts, "5.10.4"}, │ │ │ + [{kernel, "2.16.4"}, │ │ │ + {stdlib, "1.19.4"}, │ │ │ + {sasl, "2.3.4"}, │ │ │ + {pea, "1.0"}]}.

      The listed applications are not only original Erlang/OTP applications but │ │ │ possibly also new applications that you have written (here exemplified by the │ │ │ application Pea (pea)).

      Step 2. Start Erlang/OTP from the directory where the mysystem.rel file │ │ │ resides:

      % erl -pa /home/user/target_system/myapps/pea-1.0/ebin

      The -pa argument prepends the path to the ebin directory for │ │ │ the Pea application to the code path.

      Step 3. Create the target system:

      1> target_system:create("mysystem").

      The function target_system:create/1 performs the following:

      1. Reads the file mysystem.rel and creates a new file plain.rel. │ │ │ The new file is identical to the original, except that it only │ │ │ lists the Kernel and STDLIB applications.

      2. From the files mysystem.rel and plain.rel creates the files │ │ │ mysystem.script, mysystem.boot, plain.script, and plain.boot │ │ │ @@ -242,25 +242,25 @@ │ │ │ │ │ │ │ │ │ │ │ │ Creating the Next Version │ │ │ │ │ │

        In this example the Pea application has been changed, and so are the │ │ │ applications ERTS, Kernel, STDLIB and SASL.

        Step 1. Create the file .rel:

        %% mysystem2.rel
        │ │ │ -{release,
        │ │ │ - {"MYSYSTEM", "SECOND"},
        │ │ │ - {erts, "6.0"},
        │ │ │ - [{kernel, "3.0"},
        │ │ │ -  {stdlib, "2.0"},
        │ │ │ -  {sasl, "2.4"},
        │ │ │ -  {pea, "2.0"}]}.

        Step 2. Create the application upgrade file (see │ │ │ +{release, │ │ │ + {"MYSYSTEM", "SECOND"}, │ │ │ + {erts, "6.0"}, │ │ │ + [{kernel, "3.0"}, │ │ │ + {stdlib, "2.0"}, │ │ │ + {sasl, "2.4"}, │ │ │ + {pea, "2.0"}]}.

      Step 2. Create the application upgrade file (see │ │ │ appup in SASL) for Pea, for example:

      %% pea.appup
      │ │ │ -{"2.0",
      │ │ │ - [{"1.0",[{load_module,pea_lib}]}],
      │ │ │ - [{"1.0",[{load_module,pea_lib}]}]}.

      Step 3. From the directory where the file mysystem2.rel resides, start the │ │ │ +{"2.0", │ │ │ + [{"1.0",[{load_module,pea_lib}]}], │ │ │ + [{"1.0",[{load_module,pea_lib}]}]}.

    Step 3. From the directory where the file mysystem2.rel resides, start the │ │ │ Erlang/OTP system, giving the path to the new version of Pea:

    % erl -pa /home/user/target_system/myapps/pea-2.0/ebin

    Step 4. Create the release upgrade file (see relup │ │ │ in SASL):

    1> systools:make_relup("mysystem2",["mysystem"],["mysystem"],
    │ │ │      [{path,["/home/user/target_system/myapps/pea-1.0/ebin",
    │ │ │      "/my/old/erlang/lib/*/ebin"]}]).

    Here "mysystem" is the base release and "mysystem2" is the release to │ │ │ upgrade to.

    The path option is used for pointing out the old version of all applications. │ │ │ (The new versions are already in the code path - assuming of course that the │ │ │ Erlang node on which this is executed is running the correct version of │ │ │ @@ -292,291 +292,291 @@ │ │ │ {continue_after_restart,"FIRST",[]} │ │ │ heart: Tue Apr 1 12:15:10 2014: Erlang has closed. │ │ │ heart: Tue Apr 1 12:15:11 2014: Executed "/usr/local/erl-target/bin/start /usr/local/erl-target/releases/new_start_erl.data" -> 0. Terminating. │ │ │ [End]

    The above return value and output after the call to │ │ │ release_handler:install_release/1 means that the release_handler has │ │ │ restarted the node by using heart. This is always done when the upgrade │ │ │ involves a change of the applications ERTS, Kernel, STDLIB, or SASL. For more │ │ │ -information, see Upgrade when Erlang/OTP has Changed.

    The node is accessible through a new pipe:

    % /usr/local/erl-target/bin/to_erl /tmp/erlang.pipe.2

    List the available releases in the system:

    1> release_handler:which_releases().
    │ │ │ -[{"MYSYSTEM","SECOND",
    │ │ │ -  ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"],
    │ │ │ -  current},
    │ │ │ - {"MYSYSTEM","FIRST",
    │ │ │ -  ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"],
    │ │ │ -  permanent}]

    Our new release, "SECOND", is now the current release, but we can also see that │ │ │ +information, see Upgrade when Erlang/OTP has Changed.

    The node is accessible through a new pipe:

    % /usr/local/erl-target/bin/to_erl /tmp/erlang.pipe.2

    List the available releases in the system:

    1> release_handler:which_releases().
    │ │ │ +[{"MYSYSTEM","SECOND",
    │ │ │ +  ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"],
    │ │ │ +  current},
    │ │ │ + {"MYSYSTEM","FIRST",
    │ │ │ +  ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"],
    │ │ │ +  permanent}]

    Our new release, "SECOND", is now the current release, but we can also see that │ │ │ our "FIRST" release is still permanent. This means that if the node would be │ │ │ -restarted now, it would come up running the "FIRST" release again.

    Step 3. Make the new release permanent:

    2> release_handler:make_permanent("SECOND").

    Check the releases again:

    3> release_handler:which_releases().
    │ │ │ -[{"MYSYSTEM","SECOND",
    │ │ │ -  ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"],
    │ │ │ -  permanent},
    │ │ │ - {"MYSYSTEM","FIRST",
    │ │ │ -  ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"],
    │ │ │ -  old}]

    We see that the new release version is permanent, so it would be safe to │ │ │ +restarted now, it would come up running the "FIRST" release again.

    Step 3. Make the new release permanent:

    2> release_handler:make_permanent("SECOND").

    Check the releases again:

    3> release_handler:which_releases().
    │ │ │ +[{"MYSYSTEM","SECOND",
    │ │ │ +  ["kernel-3.0","stdlib-2.0","sasl-2.4","pea-2.0"],
    │ │ │ +  permanent},
    │ │ │ + {"MYSYSTEM","FIRST",
    │ │ │ +  ["kernel-2.16.4","stdlib-1.19.4","sasl-2.3.4","pea-1.0"],
    │ │ │ +  old}]

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

    │ │ │ │ │ │ │ │ │ │ │ │ Listing of target_system.erl │ │ │

    │ │ │

    This module can also be found in the examples directory of the SASL │ │ │ application.

    
    │ │ │ --module(target_system).
    │ │ │ --export([create/1, create/2, install/2]).
    │ │ │ +-module(target_system).
    │ │ │ +-export([create/1, create/2, install/2]).
    │ │ │  
    │ │ │  %% Note: RelFileName below is the *stem* without trailing .rel,
    │ │ │  %% .script etc.
    │ │ │  %%
    │ │ │  
    │ │ │  %% create(RelFileName)
    │ │ │  %%
    │ │ │ -create(RelFileName) ->
    │ │ │ -    create(RelFileName,[]).
    │ │ │ +create(RelFileName) ->
    │ │ │ +    create(RelFileName,[]).
    │ │ │  
    │ │ │ -create(RelFileName,SystoolsOpts) ->
    │ │ │ +create(RelFileName,SystoolsOpts) ->
    │ │ │      RelFile = RelFileName ++ ".rel",
    │ │ │ -    Dir = filename:dirname(RelFileName),
    │ │ │ -    PlainRelFileName = filename:join(Dir,"plain"),
    │ │ │ +    Dir = filename:dirname(RelFileName),
    │ │ │ +    PlainRelFileName = filename:join(Dir,"plain"),
    │ │ │      PlainRelFile = PlainRelFileName ++ ".rel",
    │ │ │ -    io:fwrite("Reading file: ~ts ...~n", [RelFile]),
    │ │ │ -    {ok, [RelSpec]} = file:consult(RelFile),
    │ │ │ -    io:fwrite("Creating file: ~ts from ~ts ...~n",
    │ │ │ -              [PlainRelFile, RelFile]),
    │ │ │ -    {release,
    │ │ │ -     {RelName, RelVsn},
    │ │ │ -     {erts, ErtsVsn},
    │ │ │ -     AppVsns} = RelSpec,
    │ │ │ -    PlainRelSpec = {release,
    │ │ │ -                    {RelName, RelVsn},
    │ │ │ -                    {erts, ErtsVsn},
    │ │ │ -                    lists:filter(fun({kernel, _}) ->
    │ │ │ +    io:fwrite("Reading file: ~ts ...~n", [RelFile]),
    │ │ │ +    {ok, [RelSpec]} = file:consult(RelFile),
    │ │ │ +    io:fwrite("Creating file: ~ts from ~ts ...~n",
    │ │ │ +              [PlainRelFile, RelFile]),
    │ │ │ +    {release,
    │ │ │ +     {RelName, RelVsn},
    │ │ │ +     {erts, ErtsVsn},
    │ │ │ +     AppVsns} = RelSpec,
    │ │ │ +    PlainRelSpec = {release,
    │ │ │ +                    {RelName, RelVsn},
    │ │ │ +                    {erts, ErtsVsn},
    │ │ │ +                    lists:filter(fun({kernel, _}) ->
    │ │ │                                           true;
    │ │ │ -                                    ({stdlib, _}) ->
    │ │ │ +                                    ({stdlib, _}) ->
    │ │ │                                           true;
    │ │ │ -                                    (_) ->
    │ │ │ +                                    (_) ->
    │ │ │                                           false
    │ │ │ -                                 end, AppVsns)
    │ │ │ -                   },
    │ │ │ -    {ok, Fd} = file:open(PlainRelFile, [write]),
    │ │ │ -    io:fwrite(Fd, "~p.~n", [PlainRelSpec]),
    │ │ │ -    file:close(Fd),
    │ │ │ -
    │ │ │ -    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
    │ │ │ -	      [PlainRelFileName,PlainRelFileName]),
    │ │ │ -    make_script(PlainRelFileName,SystoolsOpts),
    │ │ │ -
    │ │ │ -    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
    │ │ │ -              [RelFileName, RelFileName]),
    │ │ │ -    make_script(RelFileName,SystoolsOpts),
    │ │ │ +                                 end, AppVsns)
    │ │ │ +                   },
    │ │ │ +    {ok, Fd} = file:open(PlainRelFile, [write]),
    │ │ │ +    io:fwrite(Fd, "~p.~n", [PlainRelSpec]),
    │ │ │ +    file:close(Fd),
    │ │ │ +
    │ │ │ +    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
    │ │ │ +	      [PlainRelFileName,PlainRelFileName]),
    │ │ │ +    make_script(PlainRelFileName,SystoolsOpts),
    │ │ │ +
    │ │ │ +    io:fwrite("Making \"~ts.script\" and \"~ts.boot\" files ...~n",
    │ │ │ +              [RelFileName, RelFileName]),
    │ │ │ +    make_script(RelFileName,SystoolsOpts),
    │ │ │  
    │ │ │      TarFileName = RelFileName ++ ".tar.gz",
    │ │ │ -    io:fwrite("Creating tar file ~ts ...~n", [TarFileName]),
    │ │ │ -    make_tar(RelFileName,SystoolsOpts),
    │ │ │ +    io:fwrite("Creating tar file ~ts ...~n", [TarFileName]),
    │ │ │ +    make_tar(RelFileName,SystoolsOpts),
    │ │ │  
    │ │ │ -    TmpDir = filename:join(Dir,"tmp"),
    │ │ │ -    io:fwrite("Creating directory ~tp ...~n",[TmpDir]),
    │ │ │ -    file:make_dir(TmpDir),
    │ │ │ -
    │ │ │ -    io:fwrite("Extracting ~ts into directory ~ts ...~n", [TarFileName,TmpDir]),
    │ │ │ -    extract_tar(TarFileName, TmpDir),
    │ │ │ -
    │ │ │ -    TmpBinDir = filename:join([TmpDir, "bin"]),
    │ │ │ -    ErtsBinDir = filename:join([TmpDir, "erts-" ++ ErtsVsn, "bin"]),
    │ │ │ -    io:fwrite("Deleting \"erl\" and \"start\" in directory ~ts ...~n",
    │ │ │ -              [ErtsBinDir]),
    │ │ │ -    file:delete(filename:join([ErtsBinDir, "erl"])),
    │ │ │ -    file:delete(filename:join([ErtsBinDir, "start"])),
    │ │ │ -
    │ │ │ -    io:fwrite("Creating temporary directory ~ts ...~n", [TmpBinDir]),
    │ │ │ -    file:make_dir(TmpBinDir),
    │ │ │ -
    │ │ │ -    io:fwrite("Copying file \"~ts.boot\" to ~ts ...~n",
    │ │ │ -              [PlainRelFileName, filename:join([TmpBinDir, "start.boot"])]),
    │ │ │ -    copy_file(PlainRelFileName++".boot",filename:join([TmpBinDir, "start.boot"])),
    │ │ │ +    TmpDir = filename:join(Dir,"tmp"),
    │ │ │ +    io:fwrite("Creating directory ~tp ...~n",[TmpDir]),
    │ │ │ +    file:make_dir(TmpDir),
    │ │ │ +
    │ │ │ +    io:fwrite("Extracting ~ts into directory ~ts ...~n", [TarFileName,TmpDir]),
    │ │ │ +    extract_tar(TarFileName, TmpDir),
    │ │ │ +
    │ │ │ +    TmpBinDir = filename:join([TmpDir, "bin"]),
    │ │ │ +    ErtsBinDir = filename:join([TmpDir, "erts-" ++ ErtsVsn, "bin"]),
    │ │ │ +    io:fwrite("Deleting \"erl\" and \"start\" in directory ~ts ...~n",
    │ │ │ +              [ErtsBinDir]),
    │ │ │ +    file:delete(filename:join([ErtsBinDir, "erl"])),
    │ │ │ +    file:delete(filename:join([ErtsBinDir, "start"])),
    │ │ │ +
    │ │ │ +    io:fwrite("Creating temporary directory ~ts ...~n", [TmpBinDir]),
    │ │ │ +    file:make_dir(TmpBinDir),
    │ │ │ +
    │ │ │ +    io:fwrite("Copying file \"~ts.boot\" to ~ts ...~n",
    │ │ │ +              [PlainRelFileName, filename:join([TmpBinDir, "start.boot"])]),
    │ │ │ +    copy_file(PlainRelFileName++".boot",filename:join([TmpBinDir, "start.boot"])),
    │ │ │  
    │ │ │ -    io:fwrite("Copying files \"epmd\", \"run_erl\" and \"to_erl\" from \n"
    │ │ │ +    io:fwrite("Copying files \"epmd\", \"run_erl\" and \"to_erl\" from \n"
    │ │ │                "~ts to ~ts ...~n",
    │ │ │ -              [ErtsBinDir, TmpBinDir]),
    │ │ │ -    copy_file(filename:join([ErtsBinDir, "epmd"]),
    │ │ │ -              filename:join([TmpBinDir, "epmd"]), [preserve]),
    │ │ │ -    copy_file(filename:join([ErtsBinDir, "run_erl"]),
    │ │ │ -              filename:join([TmpBinDir, "run_erl"]), [preserve]),
    │ │ │ -    copy_file(filename:join([ErtsBinDir, "to_erl"]),
    │ │ │ -              filename:join([TmpBinDir, "to_erl"]), [preserve]),
    │ │ │ +              [ErtsBinDir, TmpBinDir]),
    │ │ │ +    copy_file(filename:join([ErtsBinDir, "epmd"]),
    │ │ │ +              filename:join([TmpBinDir, "epmd"]), [preserve]),
    │ │ │ +    copy_file(filename:join([ErtsBinDir, "run_erl"]),
    │ │ │ +              filename:join([TmpBinDir, "run_erl"]), [preserve]),
    │ │ │ +    copy_file(filename:join([ErtsBinDir, "to_erl"]),
    │ │ │ +              filename:join([TmpBinDir, "to_erl"]), [preserve]),
    │ │ │  
    │ │ │      %% This is needed if 'start' script created from 'start.src' shall
    │ │ │      %% be used as it points out this directory as log dir for 'run_erl'
    │ │ │ -    TmpLogDir = filename:join([TmpDir, "log"]),
    │ │ │ -    io:fwrite("Creating temporary directory ~ts ...~n", [TmpLogDir]),
    │ │ │ -    ok = file:make_dir(TmpLogDir),
    │ │ │ -
    │ │ │ -    StartErlDataFile = filename:join([TmpDir, "releases", "start_erl.data"]),
    │ │ │ -    io:fwrite("Creating ~ts ...~n", [StartErlDataFile]),
    │ │ │ -    StartErlData = io_lib:fwrite("~s ~s~n", [ErtsVsn, RelVsn]),
    │ │ │ -    write_file(StartErlDataFile, StartErlData),
    │ │ │ -
    │ │ │ -    io:fwrite("Recreating tar file ~ts from contents in directory ~ts ...~n",
    │ │ │ -	      [TarFileName,TmpDir]),
    │ │ │ -    {ok, Tar} = erl_tar:open(TarFileName, [write, compressed]),
    │ │ │ +    TmpLogDir = filename:join([TmpDir, "log"]),
    │ │ │ +    io:fwrite("Creating temporary directory ~ts ...~n", [TmpLogDir]),
    │ │ │ +    ok = file:make_dir(TmpLogDir),
    │ │ │ +
    │ │ │ +    StartErlDataFile = filename:join([TmpDir, "releases", "start_erl.data"]),
    │ │ │ +    io:fwrite("Creating ~ts ...~n", [StartErlDataFile]),
    │ │ │ +    StartErlData = io_lib:fwrite("~s ~s~n", [ErtsVsn, RelVsn]),
    │ │ │ +    write_file(StartErlDataFile, StartErlData),
    │ │ │ +
    │ │ │ +    io:fwrite("Recreating tar file ~ts from contents in directory ~ts ...~n",
    │ │ │ +	      [TarFileName,TmpDir]),
    │ │ │ +    {ok, Tar} = erl_tar:open(TarFileName, [write, compressed]),
    │ │ │      %% {ok, Cwd} = file:get_cwd(),
    │ │ │      %% file:set_cwd("tmp"),
    │ │ │      ErtsDir = "erts-"++ErtsVsn,
    │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"bin"), "bin", []),
    │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,ErtsDir), ErtsDir, []),
    │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"releases"), "releases", []),
    │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"lib"), "lib", []),
    │ │ │ -    erl_tar:add(Tar, filename:join(TmpDir,"log"), "log", []),
    │ │ │ -    erl_tar:close(Tar),
    │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"bin"), "bin", []),
    │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,ErtsDir), ErtsDir, []),
    │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"releases"), "releases", []),
    │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"lib"), "lib", []),
    │ │ │ +    erl_tar:add(Tar, filename:join(TmpDir,"log"), "log", []),
    │ │ │ +    erl_tar:close(Tar),
    │ │ │      %% file:set_cwd(Cwd),
    │ │ │ -    io:fwrite("Removing directory ~ts ...~n",[TmpDir]),
    │ │ │ -    remove_dir_tree(TmpDir),
    │ │ │ +    io:fwrite("Removing directory ~ts ...~n",[TmpDir]),
    │ │ │ +    remove_dir_tree(TmpDir),
    │ │ │      ok.
    │ │ │  
    │ │ │  
    │ │ │ -install(RelFileName, RootDir) ->
    │ │ │ +install(RelFileName, RootDir) ->
    │ │ │      TarFile = RelFileName ++ ".tar.gz",
    │ │ │ -    io:fwrite("Extracting ~ts ...~n", [TarFile]),
    │ │ │ -    extract_tar(TarFile, RootDir),
    │ │ │ -    StartErlDataFile = filename:join([RootDir, "releases", "start_erl.data"]),
    │ │ │ -    {ok, StartErlData} = read_txt_file(StartErlDataFile),
    │ │ │ -    [ErlVsn, _RelVsn| _] = string:tokens(StartErlData, " \n"),
    │ │ │ -    ErtsBinDir = filename:join([RootDir, "erts-" ++ ErlVsn, "bin"]),
    │ │ │ -    BinDir = filename:join([RootDir, "bin"]),
    │ │ │ -    io:fwrite("Substituting in erl.src, start.src and start_erl.src to "
    │ │ │ -              "form erl, start and start_erl ...\n"),
    │ │ │ -    subst_src_scripts(["erl", "start", "start_erl"], ErtsBinDir, BinDir,
    │ │ │ -                      [{"FINAL_ROOTDIR", RootDir}, {"EMU", "beam"}],
    │ │ │ -                      [preserve]),
    │ │ │ +    io:fwrite("Extracting ~ts ...~n", [TarFile]),
    │ │ │ +    extract_tar(TarFile, RootDir),
    │ │ │ +    StartErlDataFile = filename:join([RootDir, "releases", "start_erl.data"]),
    │ │ │ +    {ok, StartErlData} = read_txt_file(StartErlDataFile),
    │ │ │ +    [ErlVsn, _RelVsn| _] = string:tokens(StartErlData, " \n"),
    │ │ │ +    ErtsBinDir = filename:join([RootDir, "erts-" ++ ErlVsn, "bin"]),
    │ │ │ +    BinDir = filename:join([RootDir, "bin"]),
    │ │ │ +    io:fwrite("Substituting in erl.src, start.src and start_erl.src to "
    │ │ │ +              "form erl, start and start_erl ...\n"),
    │ │ │ +    subst_src_scripts(["erl", "start", "start_erl"], ErtsBinDir, BinDir,
    │ │ │ +                      [{"FINAL_ROOTDIR", RootDir}, {"EMU", "beam"}],
    │ │ │ +                      [preserve]),
    │ │ │      %%! Workaround for pre OTP 17.0: start.src and start_erl.src did
    │ │ │      %%! not have correct permissions, so the above 'preserve' option did not help
    │ │ │ -    ok = file:change_mode(filename:join(BinDir,"start"),8#0755),
    │ │ │ -    ok = file:change_mode(filename:join(BinDir,"start_erl"),8#0755),
    │ │ │ +    ok = file:change_mode(filename:join(BinDir,"start"),8#0755),
    │ │ │ +    ok = file:change_mode(filename:join(BinDir,"start_erl"),8#0755),
    │ │ │  
    │ │ │ -    io:fwrite("Creating the RELEASES file ...\n"),
    │ │ │ -    create_RELEASES(RootDir, filename:join([RootDir, "releases",
    │ │ │ -					    filename:basename(RelFileName)])).
    │ │ │ +    io:fwrite("Creating the RELEASES file ...\n"),
    │ │ │ +    create_RELEASES(RootDir, filename:join([RootDir, "releases",
    │ │ │ +					    filename:basename(RelFileName)])).
    │ │ │  
    │ │ │  %% LOCALS
    │ │ │  
    │ │ │  %% make_script(RelFileName,Opts)
    │ │ │  %%
    │ │ │ -make_script(RelFileName,Opts) ->
    │ │ │ -    systools:make_script(RelFileName, [no_module_tests,
    │ │ │ -				       {outdir,filename:dirname(RelFileName)}
    │ │ │ -				       |Opts]).
    │ │ │ +make_script(RelFileName,Opts) ->
    │ │ │ +    systools:make_script(RelFileName, [no_module_tests,
    │ │ │ +				       {outdir,filename:dirname(RelFileName)}
    │ │ │ +				       |Opts]).
    │ │ │  
    │ │ │  %% make_tar(RelFileName,Opts)
    │ │ │  %%
    │ │ │ -make_tar(RelFileName,Opts) ->
    │ │ │ -    RootDir = code:root_dir(),
    │ │ │ -    systools:make_tar(RelFileName, [{erts, RootDir},
    │ │ │ -				    {outdir,filename:dirname(RelFileName)}
    │ │ │ -				    |Opts]).
    │ │ │ +make_tar(RelFileName,Opts) ->
    │ │ │ +    RootDir = code:root_dir(),
    │ │ │ +    systools:make_tar(RelFileName, [{erts, RootDir},
    │ │ │ +				    {outdir,filename:dirname(RelFileName)}
    │ │ │ +				    |Opts]).
    │ │ │  
    │ │ │  %% extract_tar(TarFile, DestDir)
    │ │ │  %%
    │ │ │ -extract_tar(TarFile, DestDir) ->
    │ │ │ -    erl_tar:extract(TarFile, [{cwd, DestDir}, compressed]).
    │ │ │ +extract_tar(TarFile, DestDir) ->
    │ │ │ +    erl_tar:extract(TarFile, [{cwd, DestDir}, compressed]).
    │ │ │  
    │ │ │ -create_RELEASES(DestDir, RelFileName) ->
    │ │ │ -    release_handler:create_RELEASES(DestDir, RelFileName ++ ".rel").
    │ │ │ +create_RELEASES(DestDir, RelFileName) ->
    │ │ │ +    release_handler:create_RELEASES(DestDir, RelFileName ++ ".rel").
    │ │ │  
    │ │ │ -subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) ->
    │ │ │ -    lists:foreach(fun(Script) ->
    │ │ │ -                          subst_src_script(Script, SrcDir, DestDir,
    │ │ │ -                                           Vars, Opts)
    │ │ │ -                  end, Scripts).
    │ │ │ -
    │ │ │ -subst_src_script(Script, SrcDir, DestDir, Vars, Opts) ->
    │ │ │ -    subst_file(filename:join([SrcDir, Script ++ ".src"]),
    │ │ │ -               filename:join([DestDir, Script]),
    │ │ │ -               Vars, Opts).
    │ │ │ -
    │ │ │ -subst_file(Src, Dest, Vars, Opts) ->
    │ │ │ -    {ok, Conts} = read_txt_file(Src),
    │ │ │ -    NConts = subst(Conts, Vars),
    │ │ │ -    write_file(Dest, NConts),
    │ │ │ -    case lists:member(preserve, Opts) of
    │ │ │ +subst_src_scripts(Scripts, SrcDir, DestDir, Vars, Opts) ->
    │ │ │ +    lists:foreach(fun(Script) ->
    │ │ │ +                          subst_src_script(Script, SrcDir, DestDir,
    │ │ │ +                                           Vars, Opts)
    │ │ │ +                  end, Scripts).
    │ │ │ +
    │ │ │ +subst_src_script(Script, SrcDir, DestDir, Vars, Opts) ->
    │ │ │ +    subst_file(filename:join([SrcDir, Script ++ ".src"]),
    │ │ │ +               filename:join([DestDir, Script]),
    │ │ │ +               Vars, Opts).
    │ │ │ +
    │ │ │ +subst_file(Src, Dest, Vars, Opts) ->
    │ │ │ +    {ok, Conts} = read_txt_file(Src),
    │ │ │ +    NConts = subst(Conts, Vars),
    │ │ │ +    write_file(Dest, NConts),
    │ │ │ +    case lists:member(preserve, Opts) of
    │ │ │          true ->
    │ │ │ -            {ok, FileInfo} = file:read_file_info(Src),
    │ │ │ -            file:write_file_info(Dest, FileInfo);
    │ │ │ +            {ok, FileInfo} = file:read_file_info(Src),
    │ │ │ +            file:write_file_info(Dest, FileInfo);
    │ │ │          false ->
    │ │ │              ok
    │ │ │      end.
    │ │ │  
    │ │ │  %% subst(Str, Vars)
    │ │ │  %% Vars = [{Var, Val}]
    │ │ │  %% Var = Val = string()
    │ │ │  %% Substitute all occurrences of %Var% for Val in Str, using the list
    │ │ │  %% of variables in Vars.
    │ │ │  %%
    │ │ │ -subst(Str, Vars) ->
    │ │ │ -    subst(Str, Vars, []).
    │ │ │ +subst(Str, Vars) ->
    │ │ │ +    subst(Str, Vars, []).
    │ │ │  
    │ │ │ -subst([$%, C| Rest], Vars, Result) when $A =< C, C =< $Z ->
    │ │ │ -    subst_var([C| Rest], Vars, Result, []);
    │ │ │ -subst([$%, C| Rest], Vars, Result) when $a =< C, C =< $z ->
    │ │ │ -    subst_var([C| Rest], Vars, Result, []);
    │ │ │ -subst([$%, C| Rest], Vars, Result) when  C == $_ ->
    │ │ │ -    subst_var([C| Rest], Vars, Result, []);
    │ │ │ -subst([C| Rest], Vars, Result) ->
    │ │ │ -    subst(Rest, Vars, [C| Result]);
    │ │ │ -subst([], _Vars, Result) ->
    │ │ │ -    lists:reverse(Result).
    │ │ │ -
    │ │ │ -subst_var([$%| Rest], Vars, Result, VarAcc) ->
    │ │ │ -    Key = lists:reverse(VarAcc),
    │ │ │ -    case lists:keysearch(Key, 1, Vars) of
    │ │ │ -        {value, {Key, Value}} ->
    │ │ │ -            subst(Rest, Vars, lists:reverse(Value, Result));
    │ │ │ +subst([$%, C| Rest], Vars, Result) when $A =< C, C =< $Z ->
    │ │ │ +    subst_var([C| Rest], Vars, Result, []);
    │ │ │ +subst([$%, C| Rest], Vars, Result) when $a =< C, C =< $z ->
    │ │ │ +    subst_var([C| Rest], Vars, Result, []);
    │ │ │ +subst([$%, C| Rest], Vars, Result) when  C == $_ ->
    │ │ │ +    subst_var([C| Rest], Vars, Result, []);
    │ │ │ +subst([C| Rest], Vars, Result) ->
    │ │ │ +    subst(Rest, Vars, [C| Result]);
    │ │ │ +subst([], _Vars, Result) ->
    │ │ │ +    lists:reverse(Result).
    │ │ │ +
    │ │ │ +subst_var([$%| Rest], Vars, Result, VarAcc) ->
    │ │ │ +    Key = lists:reverse(VarAcc),
    │ │ │ +    case lists:keysearch(Key, 1, Vars) of
    │ │ │ +        {value, {Key, Value}} ->
    │ │ │ +            subst(Rest, Vars, lists:reverse(Value, Result));
    │ │ │          false ->
    │ │ │ -            subst(Rest, Vars, [$%| VarAcc ++ [$%| Result]])
    │ │ │ +            subst(Rest, Vars, [$%| VarAcc ++ [$%| Result]])
    │ │ │      end;
    │ │ │ -subst_var([C| Rest], Vars, Result, VarAcc) ->
    │ │ │ -    subst_var(Rest, Vars, Result, [C| VarAcc]);
    │ │ │ -subst_var([], Vars, Result, VarAcc) ->
    │ │ │ -    subst([], Vars, [VarAcc ++ [$%| Result]]).
    │ │ │ -
    │ │ │ -copy_file(Src, Dest) ->
    │ │ │ -    copy_file(Src, Dest, []).
    │ │ │ -
    │ │ │ -copy_file(Src, Dest, Opts) ->
    │ │ │ -    {ok,_} = file:copy(Src, Dest),
    │ │ │ -    case lists:member(preserve, Opts) of
    │ │ │ +subst_var([C| Rest], Vars, Result, VarAcc) ->
    │ │ │ +    subst_var(Rest, Vars, Result, [C| VarAcc]);
    │ │ │ +subst_var([], Vars, Result, VarAcc) ->
    │ │ │ +    subst([], Vars, [VarAcc ++ [$%| Result]]).
    │ │ │ +
    │ │ │ +copy_file(Src, Dest) ->
    │ │ │ +    copy_file(Src, Dest, []).
    │ │ │ +
    │ │ │ +copy_file(Src, Dest, Opts) ->
    │ │ │ +    {ok,_} = file:copy(Src, Dest),
    │ │ │ +    case lists:member(preserve, Opts) of
    │ │ │          true ->
    │ │ │ -            {ok, FileInfo} = file:read_file_info(Src),
    │ │ │ -            file:write_file_info(Dest, FileInfo);
    │ │ │ +            {ok, FileInfo} = file:read_file_info(Src),
    │ │ │ +            file:write_file_info(Dest, FileInfo);
    │ │ │          false ->
    │ │ │              ok
    │ │ │      end.
    │ │ │  
    │ │ │ -write_file(FName, Conts) ->
    │ │ │ -    Enc = file:native_name_encoding(),
    │ │ │ -    {ok, Fd} = file:open(FName, [write]),
    │ │ │ -    file:write(Fd, unicode:characters_to_binary(Conts,Enc,Enc)),
    │ │ │ -    file:close(Fd).
    │ │ │ -
    │ │ │ -read_txt_file(File) ->
    │ │ │ -    {ok, Bin} = file:read_file(File),
    │ │ │ -    {ok, binary_to_list(Bin)}.
    │ │ │ -
    │ │ │ -remove_dir_tree(Dir) ->
    │ │ │ -    remove_all_files(".", [Dir]).
    │ │ │ -
    │ │ │ -remove_all_files(Dir, Files) ->
    │ │ │ -    lists:foreach(fun(File) ->
    │ │ │ -                          FilePath = filename:join([Dir, File]),
    │ │ │ -                          case filelib:is_dir(FilePath) of
    │ │ │ +write_file(FName, Conts) ->
    │ │ │ +    Enc = file:native_name_encoding(),
    │ │ │ +    {ok, Fd} = file:open(FName, [write]),
    │ │ │ +    file:write(Fd, unicode:characters_to_binary(Conts,Enc,Enc)),
    │ │ │ +    file:close(Fd).
    │ │ │ +
    │ │ │ +read_txt_file(File) ->
    │ │ │ +    {ok, Bin} = file:read_file(File),
    │ │ │ +    {ok, binary_to_list(Bin)}.
    │ │ │ +
    │ │ │ +remove_dir_tree(Dir) ->
    │ │ │ +    remove_all_files(".", [Dir]).
    │ │ │ +
    │ │ │ +remove_all_files(Dir, Files) ->
    │ │ │ +    lists:foreach(fun(File) ->
    │ │ │ +                          FilePath = filename:join([Dir, File]),
    │ │ │ +                          case filelib:is_dir(FilePath) of
    │ │ │                                true ->
    │ │ │ -                                  {ok, DirFiles} = file:list_dir(FilePath),
    │ │ │ -                                  remove_all_files(FilePath, DirFiles),
    │ │ │ -                                  file:del_dir(FilePath);
    │ │ │ +                                  {ok, DirFiles} = file:list_dir(FilePath),
    │ │ │ +                                  remove_all_files(FilePath, DirFiles),
    │ │ │ +                                  file:del_dir(FilePath);
    │ │ │                                _ ->
    │ │ │ -                                  file:delete(FilePath)
    │ │ │ +                                  file:delete(FilePath)
    │ │ │                            end
    │ │ │ -                  end, Files).
    │ │ │ + end, Files).
    │ │ │ │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │ │ │ │ Representation of Floating Point Numbers │ │ │ │ │ │

    When working with floats you may not see what you expect when printing or doing │ │ │ arithmetic operations. This is because floats are represented by a fixed number │ │ │ of bits in a base-2 system while printed floats are represented with a base-10 │ │ │ system. Erlang uses 64-bit floats. Here are examples of this phenomenon:

    1> 0.1+0.2.
    │ │ │ -0.30000000000000004

    The real numbers 0.1 and 0.2 cannot be represented exactly as floats.

    1> {36028797018963968.0, 36028797018963968 == 36028797018963968.0,
    │ │ │ -  36028797018963970.0, 36028797018963970 == 36028797018963970.0}.
    │ │ │ -{3.602879701896397e16, true,
    │ │ │ - 3.602879701896397e16, false}.

    The value 36028797018963968 can be represented exactly as a float value but │ │ │ +0.30000000000000004

    The real numbers 0.1 and 0.2 cannot be represented exactly as floats.

    1> {36028797018963968.0, 36028797018963968 == 36028797018963968.0,
    │ │ │ +  36028797018963970.0, 36028797018963970 == 36028797018963970.0}.
    │ │ │ +{3.602879701896397e16, true,
    │ │ │ + 3.602879701896397e16, false}.

    The value 36028797018963968 can be represented exactly as a float value but │ │ │ Erlang's pretty printer rounds 36028797018963968.0 to 3.602879701896397e16 │ │ │ (=36028797018963970.0) as all values in the range │ │ │ [36028797018963966.0, 36028797018963972.0] are represented by │ │ │ 36028797018963968.0.

    For more information about floats and issues with them see:

    If you need to work with exact decimal fractions, for instance to represent │ │ │ money, it is recommended to use a library that handles that, or work in │ │ │ cents instead of dollars or euros so that decimal fractions are not needed.

    Also note that Erlang's floats do not exactly match IEEE 754 floats, │ │ │ in that neither Inf nor NaN are supported in Erlang. Any │ │ │ @@ -237,52 +237,52 @@ │ │ │ by eight are called binaries.

    Examples:

    1> <<10,20>>.
    │ │ │  <<10,20>>
    │ │ │  2> <<"ABC">>.
    │ │ │  <<"ABC">>
    │ │ │  3> <<1:1,0:1>>.
    │ │ │  <<2:2>>

    The is_bitstring/1 BIF tests whether a │ │ │ term is a bit string, and the is_binary/1 │ │ │ -BIF tests whether a term is a binary.

    Examples:

    1> is_bitstring(<<1:1>>).
    │ │ │ +BIF tests whether a term is a binary.

    Examples:

    1> is_bitstring(<<1:1>>).
    │ │ │  true
    │ │ │ -2> is_binary(<<1:1>>).
    │ │ │ +2> is_binary(<<1:1>>).
    │ │ │  false
    │ │ │ -3> is_binary(<<42>>).
    │ │ │ +3> is_binary(<<42>>).
    │ │ │  true
    │ │ │  

    For more examples, see Programming Examples.

    │ │ │ │ │ │ │ │ │ │ │ │ Reference │ │ │

    │ │ │

    A term that is unique │ │ │ among connected nodes. A reference is created by calling the │ │ │ make_ref/0 BIF. The │ │ │ is_reference/1 BIF tests whether a term │ │ │ -is a reference.

    Examples:

    1> Ref = make_ref().
    │ │ │ +is a reference.

    Examples:

    1> Ref = make_ref().
    │ │ │  #Ref<0.76482849.3801088007.198204>
    │ │ │ -2> is_reference(Ref).
    │ │ │ +2> is_reference(Ref).
    │ │ │  true

    │ │ │ │ │ │ │ │ │ │ │ │ Fun │ │ │

    │ │ │

    A fun is a functional object. Funs make it possible to create an anonymous │ │ │ function and pass the function itself — not its name — as argument to other │ │ │ -functions.

    Examples:

    1> Fun1 = fun (X) -> X+1 end.
    │ │ │ +functions.

    Examples:

    1> Fun1 = fun (X) -> X+1 end.
    │ │ │  #Fun<erl_eval.6.39074546>
    │ │ │ -2> Fun1(2).
    │ │ │ +2> Fun1(2).
    │ │ │  3

    The is_function/1 and is_function/2 │ │ │ -BIFs tests whether a term is a fun.

    Examples:

    1> F = fun() -> ok end.
    │ │ │ +BIFs tests whether a term is a fun.

    Examples:

    1> F = fun() -> ok end.
    │ │ │  #Fun<erl_eval.43.105768164>
    │ │ │ -2> is_function(F).
    │ │ │ +2> is_function(F).
    │ │ │  true
    │ │ │ -3> is_function(F, 0).
    │ │ │ +3> is_function(F, 0).
    │ │ │  true
    │ │ │ -4> is_function(F, 1).
    │ │ │ +4> is_function(F, 1).
    │ │ │  false

    Read more about funs in Fun Expressions. For more │ │ │ examples, see Programming Examples.

    │ │ │ │ │ │ │ │ │ │ │ │ Port Identifier │ │ │

    │ │ │ @@ -300,94 +300,94 @@ │ │ │ for a new process after a while.

    The BIF self/0 returns the Pid of the calling process. When │ │ │ creating a new process, the parent │ │ │ process will be able to get the Pid of the child process either via the return │ │ │ value, as is the case when calling the spawn/3 BIF, or via │ │ │ a message, which is the case when calling the │ │ │ spawn_request/5 BIF. A Pid is typically used when │ │ │ when sending a process a signal. The │ │ │ -is_pid/1 BIF tests whether a term is a Pid.

    Example:

    -module(m).
    │ │ │ --export([loop/0]).
    │ │ │ +is_pid/1 BIF tests whether a term is a Pid.

    Example:

    -module(m).
    │ │ │ +-export([loop/0]).
    │ │ │  
    │ │ │ -loop() ->
    │ │ │ +loop() ->
    │ │ │      receive
    │ │ │          who_are_you ->
    │ │ │ -            io:format("I am ~p~n", [self()]),
    │ │ │ -            loop()
    │ │ │ +            io:format("I am ~p~n", [self()]),
    │ │ │ +            loop()
    │ │ │      end.
    │ │ │  
    │ │ │ -1> P = spawn(m, loop, []).
    │ │ │ +1> P = spawn(m, loop, []).
    │ │ │  <0.58.0>
    │ │ │  2> P ! who_are_you.
    │ │ │  I am <0.58.0>
    │ │ │  who_are_you

    Read more about processes in Processes.

    │ │ │ │ │ │ │ │ │ │ │ │ Tuple │ │ │

    │ │ │

    A tuple is a compound data type with a fixed number of terms:

    {Term1,...,TermN}

    Each term Term in the tuple is called an element. The number of elements is │ │ │ -said to be the size of the tuple.

    There exists a number of BIFs to manipulate tuples.

    Examples:

    1> P = {adam,24,{july,29}}.
    │ │ │ -{adam,24,{july,29}}
    │ │ │ -2> element(1,P).
    │ │ │ +said to be the size of the tuple.

    There exists a number of BIFs to manipulate tuples.

    Examples:

    1> P = {adam,24,{july,29}}.
    │ │ │ +{adam,24,{july,29}}
    │ │ │ +2> element(1,P).
    │ │ │  adam
    │ │ │ -3> element(3,P).
    │ │ │ -{july,29}
    │ │ │ -4> P2 = setelement(2,P,25).
    │ │ │ -{adam,25,{july,29}}
    │ │ │ -5> tuple_size(P).
    │ │ │ +3> element(3,P).
    │ │ │ +{july,29}
    │ │ │ +4> P2 = setelement(2,P,25).
    │ │ │ +{adam,25,{july,29}}
    │ │ │ +5> tuple_size(P).
    │ │ │  3
    │ │ │ -6> tuple_size({}).
    │ │ │ +6> tuple_size({}).
    │ │ │  0
    │ │ │ -7> is_tuple({a,b,c}).
    │ │ │ +7> is_tuple({a,b,c}).
    │ │ │  true

    │ │ │ │ │ │ │ │ │ │ │ │ Map │ │ │

    │ │ │

    A map is a compound data type with a variable number of key-value associations:

    #{Key1 => Value1, ..., KeyN => ValueN}

    Each key-value association in the map is called an association pair. The key │ │ │ and value parts of the pair are called elements. The number of association │ │ │ -pairs is said to be the size of the map.

    There exists a number of BIFs to manipulate maps.

    Examples:

    1> M1 = #{name => adam, age => 24, date => {july,29}}.
    │ │ │ -#{age => 24,date => {july,29},name => adam}
    │ │ │ -2> maps:get(name, M1).
    │ │ │ +pairs is said to be the size of the map.

    There exists a number of BIFs to manipulate maps.

    Examples:

    1> M1 = #{name => adam, age => 24, date => {july,29}}.
    │ │ │ +#{age => 24,date => {july,29},name => adam}
    │ │ │ +2> maps:get(name, M1).
    │ │ │  adam
    │ │ │ -3> maps:get(date, M1).
    │ │ │ -{july,29}
    │ │ │ -4> M2 = maps:update(age, 25, M1).
    │ │ │ -#{age => 25,date => {july,29},name => adam}
    │ │ │ -5> map_size(M).
    │ │ │ +3> maps:get(date, M1).
    │ │ │ +{july,29}
    │ │ │ +4> M2 = maps:update(age, 25, M1).
    │ │ │ +#{age => 25,date => {july,29},name => adam}
    │ │ │ +5> map_size(M).
    │ │ │  3
    │ │ │ -6> map_size(#{}).
    │ │ │ +6> map_size(#{}).
    │ │ │  0

    A collection of maps processing functions are found in module maps │ │ │ in STDLIB.

    Read more about maps in Map Expressions.

    Change

    Maps were introduced as an experimental feature in Erlang/OTP R17. Their │ │ │ functionality was extended and became fully supported in Erlang/OTP 18.

    │ │ │ │ │ │ │ │ │ │ │ │ List │ │ │

    │ │ │

    A list is a compound data type with a variable number of terms.

    [Term1,...,TermN]

    Each term Term in the list is called an element. The number of elements is │ │ │ said to be the length of the list.

    Formally, a list is either the empty list [] or consists of a head (first │ │ │ element) and a tail (remainder of the list). The tail is also a list. The │ │ │ latter can be expressed as [H|T]. The notation [Term1,...,TermN] above is │ │ │ equivalent with the list [Term1|[...|[TermN|[]]]].

    Example:

    [] is a list, thus
    [c|[]] is a list, thus
    [b|[c|[]]] is a list, thus
    [a|[b|[c|[]]]] is a list, or in short [a,b,c]

    A list where the tail is a list is sometimes called a proper list. It is │ │ │ allowed to have a list where the tail is not a list, for example, [a|b]. │ │ │ -However, this type of list is of little practical use.

    Examples:

    1> L1 = [a,2,{c,4}].
    │ │ │ -[a,2,{c,4}]
    │ │ │ -2> [H|T] = L1.
    │ │ │ -[a,2,{c,4}]
    │ │ │ +However, this type of list is of little practical use.

    Examples:

    1> L1 = [a,2,{c,4}].
    │ │ │ +[a,2,{c,4}]
    │ │ │ +2> [H|T] = L1.
    │ │ │ +[a,2,{c,4}]
    │ │ │  3> H.
    │ │ │  a
    │ │ │  4> T.
    │ │ │ -[2,{c,4}]
    │ │ │ -5> L2 = [d|T].
    │ │ │ -[d,2,{c,4}]
    │ │ │ -6> length(L1).
    │ │ │ +[2,{c,4}]
    │ │ │ +5> L2 = [d|T].
    │ │ │ +[d,2,{c,4}]
    │ │ │ +6> length(L1).
    │ │ │  3
    │ │ │ -7> length([]).
    │ │ │ +7> length([]).
    │ │ │  0

    A collection of list processing functions are found in module │ │ │ lists in STDLIB.

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

    │ │ │ @@ -507,41 +507,41 @@ │ │ │ Record │ │ │ │ │ │

    A record is a data structure for storing a fixed number of elements. It has │ │ │ named fields and is similar to a struct in C. However, a record is not a true │ │ │ data type. Instead, record expressions are translated to tuple expressions │ │ │ during compilation. Therefore, record expressions are not understood by the │ │ │ shell unless special actions are taken. For details, see module shell │ │ │ -in STDLIB.

    Examples:

    -module(person).
    │ │ │ --export([new/2]).
    │ │ │ +in STDLIB.

    Examples:

    -module(person).
    │ │ │ +-export([new/2]).
    │ │ │  
    │ │ │ --record(person, {name, age}).
    │ │ │ +-record(person, {name, age}).
    │ │ │  
    │ │ │ -new(Name, Age) ->
    │ │ │ -    #person{name=Name, age=Age}.
    │ │ │ +new(Name, Age) ->
    │ │ │ +    #person{name=Name, age=Age}.
    │ │ │  
    │ │ │ -1> person:new(ernie, 44).
    │ │ │ -{person,ernie,44}

    Read more about records in Records. More examples are │ │ │ +1> person:new(ernie, 44). │ │ │ +{person,ernie,44}

    Read more about records in Records. More examples are │ │ │ found in Programming Examples.

    │ │ │ │ │ │ │ │ │ │ │ │ Boolean │ │ │

    │ │ │

    There is no Boolean data type in Erlang. Instead the atoms true and false │ │ │ are used to denote Boolean values. The is_boolean/1 │ │ │ BIF tests whether a term is a boolean.

    Examples:

    1> 2 =< 3.
    │ │ │  true
    │ │ │  2> true or false.
    │ │ │  true
    │ │ │ -3> is_boolean(true).
    │ │ │ +3> is_boolean(true).
    │ │ │  true
    │ │ │ -4> is_boolean(false).
    │ │ │ +4> is_boolean(false).
    │ │ │  true
    │ │ │ -5> is_boolean(ok).
    │ │ │ +5> is_boolean(ok).
    │ │ │  false

    │ │ │ │ │ │ │ │ │ │ │ │ Escape Sequences │ │ │

    │ │ │

    Within strings ("-delimited), quoted atoms, and the content of │ │ │ @@ -559,47 +559,47 @@ │ │ │ ~b or ~s sigils the escape sequences for normal │ │ │ strings, above, are used.

    Change

    Triple-quoted strings and sigils were introduced in Erlang/OTP 27.

    │ │ │ │ │ │ │ │ │ │ │ │ Type Conversions │ │ │

    │ │ │ -

    There are a number of BIFs for type conversions.

    Examples:

    1> atom_to_list(hello).
    │ │ │ +

    There are a number of BIFs for type conversions.

    Examples:

    1> atom_to_list(hello).
    │ │ │  "hello"
    │ │ │ -2> list_to_atom("hello").
    │ │ │ +2> list_to_atom("hello").
    │ │ │  hello
    │ │ │ -3> binary_to_list(<<"hello">>).
    │ │ │ +3> binary_to_list(<<"hello">>).
    │ │ │  "hello"
    │ │ │ -4> binary_to_list(<<104,101,108,108,111>>).
    │ │ │ +4> binary_to_list(<<104,101,108,108,111>>).
    │ │ │  "hello"
    │ │ │ -5> list_to_binary("hello").
    │ │ │ -<<104,101,108,108,111>>
    │ │ │ -6> float_to_list(7.0).
    │ │ │ +5> list_to_binary("hello").
    │ │ │ +<<104,101,108,108,111>>
    │ │ │ +6> float_to_list(7.0).
    │ │ │  "7.00000000000000000000e+00"
    │ │ │ -7> list_to_float("7.000e+00").
    │ │ │ +7> list_to_float("7.000e+00").
    │ │ │  7.0
    │ │ │ -8> integer_to_list(77).
    │ │ │ +8> integer_to_list(77).
    │ │ │  "77"
    │ │ │ -9> list_to_integer("77").
    │ │ │ +9> list_to_integer("77").
    │ │ │  77
    │ │ │ -10> tuple_to_list({a,b,c}).
    │ │ │ -[a,b,c]
    │ │ │ -11> list_to_tuple([a,b,c]).
    │ │ │ -{a,b,c}
    │ │ │ -12> term_to_binary({a,b,c}).
    │ │ │ -<<131,104,3,100,0,1,97,100,0,1,98,100,0,1,99>>
    │ │ │ -13> binary_to_term(<<131,104,3,100,0,1,97,100,0,1,98,100,0,1,99>>).
    │ │ │ -{a,b,c}
    │ │ │ -14> binary_to_integer(<<"77">>).
    │ │ │ +10> tuple_to_list({a,b,c}).
    │ │ │ +[a,b,c]
    │ │ │ +11> list_to_tuple([a,b,c]).
    │ │ │ +{a,b,c}
    │ │ │ +12> term_to_binary({a,b,c}).
    │ │ │ +<<131,104,3,100,0,1,97,100,0,1,98,100,0,1,99>>
    │ │ │ +13> binary_to_term(<<131,104,3,100,0,1,97,100,0,1,98,100,0,1,99>>).
    │ │ │ +{a,b,c}
    │ │ │ +14> binary_to_integer(<<"77">>).
    │ │ │  77
    │ │ │ -15> integer_to_binary(77).
    │ │ │ -<<"77">>
    │ │ │ -16> float_to_binary(7.0).
    │ │ │ -<<"7.00000000000000000000e+00">>
    │ │ │ -17> binary_to_float(<<"7.000e+00">>).
    │ │ │ +15> integer_to_binary(77).
    │ │ │ +<<"77">>
    │ │ │ +16> float_to_binary(7.0).
    │ │ │ +<<"7.00000000000000000000e+00">>
    │ │ │ +17> binary_to_float(<<"7.000e+00">>).
    │ │ │  7.0
    │ │ │ │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │

    -module(ch1).
    │ │ │ --export([start/0]).
    │ │ │ --export([alloc/0, free/1]).
    │ │ │ --export([init/0]).
    │ │ │ +respectively.

    -module(ch1).
    │ │ │ +-export([start/0]).
    │ │ │ +-export([alloc/0, free/1]).
    │ │ │ +-export([init/0]).
    │ │ │  
    │ │ │ -start() ->
    │ │ │ -    spawn(ch1, init, []).
    │ │ │ +start() ->
    │ │ │ +    spawn(ch1, init, []).
    │ │ │  
    │ │ │ -alloc() ->
    │ │ │ -    ch1 ! {self(), alloc},
    │ │ │ +alloc() ->
    │ │ │ +    ch1 ! {self(), alloc},
    │ │ │      receive
    │ │ │ -        {ch1, Res} ->
    │ │ │ +        {ch1, Res} ->
    │ │ │              Res
    │ │ │      end.
    │ │ │  
    │ │ │ -free(Ch) ->
    │ │ │ -    ch1 ! {free, Ch},
    │ │ │ +free(Ch) ->
    │ │ │ +    ch1 ! {free, Ch},
    │ │ │      ok.
    │ │ │  
    │ │ │ -init() ->
    │ │ │ -    register(ch1, self()),
    │ │ │ -    Chs = channels(),
    │ │ │ -    loop(Chs).
    │ │ │ +init() ->
    │ │ │ +    register(ch1, self()),
    │ │ │ +    Chs = channels(),
    │ │ │ +    loop(Chs).
    │ │ │  
    │ │ │ -loop(Chs) ->
    │ │ │ +loop(Chs) ->
    │ │ │      receive
    │ │ │ -        {From, alloc} ->
    │ │ │ -            {Ch, Chs2} = alloc(Chs),
    │ │ │ -            From ! {ch1, Ch},
    │ │ │ -            loop(Chs2);
    │ │ │ -        {free, Ch} ->
    │ │ │ -            Chs2 = free(Ch, Chs),
    │ │ │ -            loop(Chs2)
    │ │ │ -    end.

    The code for the server can be rewritten into a generic part server.erl:

    -module(server).
    │ │ │ --export([start/1]).
    │ │ │ --export([call/2, cast/2]).
    │ │ │ --export([init/1]).
    │ │ │ +        {From, alloc} ->
    │ │ │ +            {Ch, Chs2} = alloc(Chs),
    │ │ │ +            From ! {ch1, Ch},
    │ │ │ +            loop(Chs2);
    │ │ │ +        {free, Ch} ->
    │ │ │ +            Chs2 = free(Ch, Chs),
    │ │ │ +            loop(Chs2)
    │ │ │ +    end.

    The code for the server can be rewritten into a generic part server.erl:

    -module(server).
    │ │ │ +-export([start/1]).
    │ │ │ +-export([call/2, cast/2]).
    │ │ │ +-export([init/1]).
    │ │ │  
    │ │ │ -start(Mod) ->
    │ │ │ -    spawn(server, init, [Mod]).
    │ │ │ +start(Mod) ->
    │ │ │ +    spawn(server, init, [Mod]).
    │ │ │  
    │ │ │ -call(Name, Req) ->
    │ │ │ -    Name ! {call, self(), Req},
    │ │ │ +call(Name, Req) ->
    │ │ │ +    Name ! {call, self(), Req},
    │ │ │      receive
    │ │ │ -        {Name, Res} ->
    │ │ │ +        {Name, Res} ->
    │ │ │              Res
    │ │ │      end.
    │ │ │  
    │ │ │ -cast(Name, Req) ->
    │ │ │ -    Name ! {cast, Req},
    │ │ │ +cast(Name, Req) ->
    │ │ │ +    Name ! {cast, Req},
    │ │ │      ok.
    │ │ │  
    │ │ │ -init(Mod) ->
    │ │ │ -    register(Mod, self()),
    │ │ │ -    State = Mod:init(),
    │ │ │ -    loop(Mod, State).
    │ │ │ +init(Mod) ->
    │ │ │ +    register(Mod, self()),
    │ │ │ +    State = Mod:init(),
    │ │ │ +    loop(Mod, State).
    │ │ │  
    │ │ │ -loop(Mod, State) ->
    │ │ │ +loop(Mod, State) ->
    │ │ │      receive
    │ │ │ -        {call, From, Req} ->
    │ │ │ -            {Res, State2} = Mod:handle_call(Req, State),
    │ │ │ -            From ! {Mod, Res},
    │ │ │ -            loop(Mod, State2);
    │ │ │ -        {cast, Req} ->
    │ │ │ -            State2 = Mod:handle_cast(Req, State),
    │ │ │ -            loop(Mod, State2)
    │ │ │ -    end.

    And a callback module ch2.erl:

    -module(ch2).
    │ │ │ --export([start/0]).
    │ │ │ --export([alloc/0, free/1]).
    │ │ │ --export([init/0, handle_call/2, handle_cast/2]).
    │ │ │ -
    │ │ │ -start() ->
    │ │ │ -    server:start(ch2).
    │ │ │ -
    │ │ │ -alloc() ->
    │ │ │ -    server:call(ch2, alloc).
    │ │ │ -
    │ │ │ -free(Ch) ->
    │ │ │ -    server:cast(ch2, {free, Ch}).
    │ │ │ +        {call, From, Req} ->
    │ │ │ +            {Res, State2} = Mod:handle_call(Req, State),
    │ │ │ +            From ! {Mod, Res},
    │ │ │ +            loop(Mod, State2);
    │ │ │ +        {cast, Req} ->
    │ │ │ +            State2 = Mod:handle_cast(Req, State),
    │ │ │ +            loop(Mod, State2)
    │ │ │ +    end.

    And a callback module ch2.erl:

    -module(ch2).
    │ │ │ +-export([start/0]).
    │ │ │ +-export([alloc/0, free/1]).
    │ │ │ +-export([init/0, handle_call/2, handle_cast/2]).
    │ │ │ +
    │ │ │ +start() ->
    │ │ │ +    server:start(ch2).
    │ │ │ +
    │ │ │ +alloc() ->
    │ │ │ +    server:call(ch2, alloc).
    │ │ │ +
    │ │ │ +free(Ch) ->
    │ │ │ +    server:cast(ch2, {free, Ch}).
    │ │ │  
    │ │ │ -init() ->
    │ │ │ -    channels().
    │ │ │ +init() ->
    │ │ │ +    channels().
    │ │ │  
    │ │ │ -handle_call(alloc, Chs) ->
    │ │ │ -    alloc(Chs). % => {Ch,Chs2}
    │ │ │ +handle_call(alloc, Chs) ->
    │ │ │ +    alloc(Chs). % => {Ch,Chs2}
    │ │ │  
    │ │ │ -handle_cast({free, Ch}, Chs) ->
    │ │ │ -    free(Ch, Chs). % => Chs2

    Notice the following:

    • The code in server can be reused to build many different servers.
    • The server name, in this example the atom ch2, is hidden from the users of │ │ │ +handle_cast({free, Ch}, Chs) -> │ │ │ + free(Ch, Chs). % => Chs2

    Notice the following:

    • The code in server can be reused to build many different servers.
    • The server name, in this example the atom ch2, is hidden from the users of │ │ │ the client functions. This means that the name can be changed without │ │ │ affecting them.
    • The protocol (messages sent to and received from the server) is also hidden. │ │ │ This is good programming practice and allows one to change the protocol │ │ │ without changing the code using the interface functions.
    • The functionality of server can be extended without having to change ch2 │ │ │ or any other callback module.

    In ch1.erl and ch2.erl above, the implementation of channels/0, alloc/1, │ │ │ and free/2 has been intentionally left out, as it is not relevant to the │ │ │ example. For completeness, one way to write these functions is given below. This │ │ │ is an example only, a realistic implementation must be able to handle situations │ │ │ -like running out of channels to allocate, and so on.

    channels() ->
    │ │ │ -   {_Allocated = [], _Free = lists:seq(1, 100)}.
    │ │ │ +like running out of channels to allocate, and so on.

    channels() ->
    │ │ │ +   {_Allocated = [], _Free = lists:seq(1, 100)}.
    │ │ │  
    │ │ │ -alloc({Allocated, [H|T] = _Free}) ->
    │ │ │ -   {H, {[H|Allocated], T}}.
    │ │ │ +alloc({Allocated, [H|T] = _Free}) ->
    │ │ │ +   {H, {[H|Allocated], T}}.
    │ │ │  
    │ │ │ -free(Ch, {Alloc, Free} = Channels) ->
    │ │ │ -   case lists:member(Ch, Alloc) of
    │ │ │ +free(Ch, {Alloc, Free} = Channels) ->
    │ │ │ +   case lists:member(Ch, Alloc) of
    │ │ │        true ->
    │ │ │ -         {lists:delete(Ch, Alloc), [Ch|Free]};
    │ │ │ +         {lists:delete(Ch, Alloc), [Ch|Free]};
    │ │ │        false ->
    │ │ │           Channels
    │ │ │     end.

    Code written without using behaviours can be more efficient, but the increased │ │ │ efficiency is at the expense of generality. The ability to manage all │ │ │ applications in the system in a consistent manner is important.

    Using behaviours also makes it easier to read and understand code written by │ │ │ other programmers. Improvised programming structures, while possibly more │ │ │ efficient, are always more difficult to understand.

    The server module corresponds, greatly simplified, to the Erlang/OTP behaviour │ │ │ gen_server.

    The standard Erlang/OTP behaviours are:

    • gen_server

      For implementing the server of a client-server relation

    • gen_statem

      For implementing state machines

    • gen_event

      For implementing event handling functionality

    • supervisor

      For implementing a supervisor in a supervision tree

    The compiler understands the module attribute -behaviour(Behaviour) and issues │ │ │ -warnings about missing callback functions, for example:

    -module(chs3).
    │ │ │ --behaviour(gen_server).
    │ │ │ +warnings about missing callback functions, for example:

    -module(chs3).
    │ │ │ +-behaviour(gen_server).
    │ │ │  ...
    │ │ │  
    │ │ │ -3> c(chs3).
    │ │ │ +3> c(chs3).
    │ │ │  ./chs3.erl:10: Warning: undefined call-back function handle_call/3
    │ │ │ -{ok,chs3}

    │ │ │ +{ok,chs3}

    │ │ │ │ │ │ │ │ │ │ │ │ Applications │ │ │

    │ │ │

    Erlang/OTP comes with a number of components, each implementing some specific │ │ │ functionality. Components are with Erlang/OTP terminology called applications. │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/distributed.html │ │ │ @@ -142,25 +142,25 @@ │ │ │ │ │ │

    A node is an executing Erlang runtime system that has been given a name, using │ │ │ the command-line flag -name (long names) or │ │ │ -sname (short names).

    The format of the node name is an atom name@host. name is the name given by │ │ │ the user. host is the full host name if long names are used, or the first part │ │ │ of the host name if short names are used. Function node() │ │ │ returns the name of the node.

    Example:

    % erl -name dilbert
    │ │ │ -(dilbert@uab.ericsson.se)1> node().
    │ │ │ +(dilbert@uab.ericsson.se)1> node().
    │ │ │  'dilbert@uab.ericsson.se'
    │ │ │  
    │ │ │  % erl -sname dilbert
    │ │ │ -(dilbert@uab)1> node().
    │ │ │ +(dilbert@uab)1> node().
    │ │ │  dilbert@uab

    The node name can also be given in runtime by calling net_kernel:start/1.

    Example:

    % erl
    │ │ │ -1> node().
    │ │ │ +1> node().
    │ │ │  nonode@nohost
    │ │ │ -2> net_kernel:start([dilbert,shortnames]).
    │ │ │ -{ok,<0.102.0>}
    │ │ │ -(dilbert@uab)3> node().
    │ │ │ +2> net_kernel:start([dilbert,shortnames]).
    │ │ │ +{ok,<0.102.0>}
    │ │ │ +(dilbert@uab)3> node().
    │ │ │  dilbert@uab

    Note

    A node with a long node name cannot communicate with a node with a short node │ │ │ name.

    │ │ │ │ │ │ │ │ │ │ │ │ Node Connections │ │ │

    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/distributed_applications.html │ │ │ @@ -150,36 +150,36 @@ │ │ │ (within the time-out specified by sync_nodes_timeout).
  • sync_nodes_timeout = integer() | infinity - Specifies how many milliseconds │ │ │ to wait for the other nodes to start.

  • When started, the node waits for all nodes specified by sync_nodes_mandatory │ │ │ and sync_nodes_optional to come up. When all nodes are up, or when all │ │ │ mandatory nodes are up and the time specified by sync_nodes_timeout has │ │ │ elapsed, all applications start. If not all mandatory nodes are up, the node │ │ │ terminates.

    Example:

    An application myapp is to run at the node cp1@cave. If this node goes down, │ │ │ myapp is to be restarted at cp2@cave or cp3@cave. A system configuration │ │ │ -file cp1.config for cp1@cave can look as follows:

    [{kernel,
    │ │ │ -  [{distributed, [{myapp, 5000, [cp1@cave, {cp2@cave, cp3@cave}]}]},
    │ │ │ -   {sync_nodes_mandatory, [cp2@cave, cp3@cave]},
    │ │ │ -   {sync_nodes_timeout, 5000}
    │ │ │ -  ]
    │ │ │ - }
    │ │ │ -].

    The system configuration files for cp2@cave and cp3@cave are identical, │ │ │ +file cp1.config for cp1@cave can look as follows:

    [{kernel,
    │ │ │ +  [{distributed, [{myapp, 5000, [cp1@cave, {cp2@cave, cp3@cave}]}]},
    │ │ │ +   {sync_nodes_mandatory, [cp2@cave, cp3@cave]},
    │ │ │ +   {sync_nodes_timeout, 5000}
    │ │ │ +  ]
    │ │ │ + }
    │ │ │ +].

    The system configuration files for cp2@cave and cp3@cave are identical, │ │ │ except for the list of mandatory nodes, which is to be [cp1@cave, cp3@cave] │ │ │ for cp2@cave and [cp1@cave, cp2@cave] for cp3@cave.

    Note

    All involved nodes must have the same value for distributed and │ │ │ sync_nodes_timeout. Otherwise the system behavior is undefined.

    │ │ │ │ │ │ │ │ │ │ │ │ Starting and Stopping Distributed Applications │ │ │

    │ │ │

    When all involved (mandatory) nodes have been started, the distributed │ │ │ application can be started by calling application:start(Application) at all │ │ │ of these nodes.

    A boot script (see Releases) can be used that │ │ │ automatically starts the application.

    The application is started at the first operational node that is listed in the │ │ │ list of nodes in the distributed configuration parameter. The application is │ │ │ started as usual. That is, an application master is created and calls the │ │ │ -application callback function:

    Module:start(normal, StartArgs)

    Example:

    Continuing the example from the previous section, the three nodes are started, │ │ │ +application callback function:

    Module:start(normal, StartArgs)

    Example:

    Continuing the example from the previous section, the three nodes are started, │ │ │ specifying the system configuration file:

    > erl -sname cp1 -config cp1
    │ │ │  > erl -sname cp2 -config cp2
    │ │ │  > erl -sname cp3 -config cp3

    When all nodes are operational, myapp can be started. This is achieved by │ │ │ calling application:start(myapp) at all three nodes. It is then started at │ │ │ cp1, as shown in the following figure:

    Application myapp - Situation 1

    Similarly, the application must be stopped by calling │ │ │ application:stop(Application) at all involved nodes.

    │ │ │ │ │ │ @@ -187,30 +187,30 @@ │ │ │ │ │ │ Failover │ │ │

    │ │ │

    If the node where the application is running goes down, the application is │ │ │ restarted (after the specified time-out) at the first operational node that is │ │ │ listed in the list of nodes in the distributed configuration parameter. This │ │ │ is called a failover.

    The application is started the normal way at the new node, that is, by the │ │ │ -application master calling:

    Module:start(normal, StartArgs)

    An exception is if the application has the start_phases key defined (see │ │ │ +application master calling:

    Module:start(normal, StartArgs)

    An exception is if the application has the start_phases key defined (see │ │ │ Included Applications). The application is then │ │ │ -instead started by calling:

    Module:start({failover, Node}, StartArgs)

    Here Node is the terminated node.

    Example:

    If cp1 goes down, the system checks which one of the other nodes, cp2 or │ │ │ +instead started by calling:

    Module:start({failover, Node}, StartArgs)

    Here Node is the terminated node.

    Example:

    If cp1 goes down, the system checks which one of the other nodes, cp2 or │ │ │ cp3, has the least number of running applications, but waits for 5 seconds for │ │ │ cp1 to restart. If cp1 does not restart and cp2 runs fewer applications │ │ │ than cp3, myapp is restarted on cp2.

    Application myapp - Situation 2

    Suppose now that cp2 goes also down and does not restart within 5 seconds. │ │ │ myapp is now restarted on cp3.

    Application myapp - Situation 3

    │ │ │ │ │ │ │ │ │ │ │ │ Takeover │ │ │

    │ │ │

    If a node is started, which has higher priority according to distributed than │ │ │ the node where a distributed application is running, the application is │ │ │ restarted at the new node and stopped at the old node. This is called a │ │ │ -takeover.

    The application is started by the application master calling:

    Module:start({takeover, Node}, StartArgs)

    Here Node is the old node.

    Example:

    If myapp is running at cp3, and if cp2 now restarts, it does not restart │ │ │ +takeover.

    The application is started by the application master calling:

    Module:start({takeover, Node}, StartArgs)

    Here Node is the old node.

    Example:

    If myapp is running at cp3, and if cp2 now restarts, it does not restart │ │ │ myapp, as the order between the cp2 and cp3 nodes is undefined.

    Application myapp - Situation 4

    However, if cp1 also restarts, the function application:takeover/2 moves │ │ │ myapp to cp1, as cp1 has a higher priority than cp3 for this │ │ │ application. In this case, Module:start({takeover, cp3@cave}, StartArgs) is │ │ │ executed at cp1 to start the application.

    Application myapp - Situation 5

    │ │ │
    │ │ │ │ │ │
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/documentation.html │ │ │ @@ -112,23 +112,23 @@ │ │ │ │ │ │ │ │ │ │ │ │ Documentation │ │ │ │ │ │ │ │ │

    Documentation in Erlang is done through the -moduledoc and -doc │ │ │ -attributes. For example:

    -module(arith).
    │ │ │ +attributes. For example:

    -module(arith).
    │ │ │  -moduledoc """
    │ │ │  A module for basic arithmetic.
    │ │ │  """.
    │ │ │  
    │ │ │ --export([add/2]).
    │ │ │ +-export([add/2]).
    │ │ │  
    │ │ │  -doc "Adds two numbers.".
    │ │ │ -add(One, Two) -> One + Two.

    The -moduledoc attribute has to be located before the first -doc attribute │ │ │ +add(One, Two) -> One + Two.

    The -moduledoc attribute has to be located before the first -doc attribute │ │ │ or function declaration. It documents the overall purpose of the module.

    The -doc attribute always precedes the function or │ │ │ attribute it documents. The │ │ │ attributes that can be documented are │ │ │ user-defined types │ │ │ (-type and -opaque) and │ │ │ behaviour module attributes │ │ │ (-callback).

    By default the format used for documentation attributes is │ │ │ @@ -140,55 +140,55 @@ │ │ │ Documentation Attributes.

    -doc attributes have been available since Erlang/OTP 27.

    │ │ │ │ │ │ │ │ │ │ │ │ Documentation metadata │ │ │

    │ │ │

    It is possible to add metadata to the documentation entry. You do this by adding │ │ │ -a -moduledoc or -doc attribute with a map as argument. For example:

    -module(arith).
    │ │ │ +a -moduledoc or -doc attribute with a map as argument. For example:

    -module(arith).
    │ │ │  -moduledoc """
    │ │ │  A module for basic arithmetic.
    │ │ │  """.
    │ │ │ --moduledoc #{since => "1.0"}.
    │ │ │ +-moduledoc #{since => "1.0"}.
    │ │ │  
    │ │ │ --export([add/2]).
    │ │ │ +-export([add/2]).
    │ │ │  
    │ │ │  -doc "Adds two numbers.".
    │ │ │ --doc(#{since => "1.0"}).
    │ │ │ -add(One, Two) -> One + Two.

    The metadata is used by documentation tools to provide extra information to the │ │ │ +-doc(#{since => "1.0"}). │ │ │ +add(One, Two) -> One + Two.

    The metadata is used by documentation tools to provide extra information to the │ │ │ user. There can be multiple metadata documentation entries, in which case the │ │ │ maps will be merged with the latest taking precedence if there are duplicate │ │ │ keys. Example:

    -doc "Adds two numbers.".
    │ │ │ --doc #{since => "1.0", author => "Joe"}.
    │ │ │ --doc #{since => "2.0"}.
    │ │ │ -add(One, Two) -> One + Two.

    This will result in a metadata entry of #{since => "2.0", author => "Joe"}.

    The keys and values in the metadata map can be any type, but it is recommended │ │ │ +-doc #{since => "1.0", author => "Joe"}. │ │ │ +-doc #{since => "2.0"}. │ │ │ +add(One, Two) -> One + Two.

    This will result in a metadata entry of #{since => "2.0", author => "Joe"}.

    The keys and values in the metadata map can be any type, but it is recommended │ │ │ that only atoms are used for keys and │ │ │ strings for the values.

    │ │ │ │ │ │ │ │ │ │ │ │ External documentation files │ │ │

    │ │ │

    The -moduledoc and -doc can also be placed in external files. To do so use │ │ │ -doc {file, "path/to/doc.md"} to point to the documentation. The path used is │ │ │ relative to the file where the -doc attribute is located. For example:

    %% doc/add.md
    │ │ │  Adds two numbers.

    and

    %% src/arith.erl
    │ │ │ --doc({file, "../doc/add.md"}).
    │ │ │ -add(One, Two) -> One + Two.

    │ │ │ +-doc({file, "../doc/add.md"}). │ │ │ +add(One, Two) -> One + Two.

    │ │ │ │ │ │ │ │ │ │ │ │ Documenting a module │ │ │

    │ │ │

    The module description should include details on how to use the API and examples │ │ │ of the different functions working together. Here is a good place to use images │ │ │ and other diagrams to better show the usage of the module. Instead of writing a │ │ │ long text in the moduledoc attribute, it could be better to break it out into │ │ │ an external page.

    The moduledoc attribute should start with a short paragraph describing the │ │ │ -module and then go into greater details. For example:

    -module(arith).
    │ │ │ +module and then go into greater details. For example:

    -module(arith).
    │ │ │  -moduledoc """
    │ │ │     A module for basic arithmetic.
    │ │ │  
    │ │ │     This module can be used to add and subtract values. For example:
    │ │ │  
    │ │ │     ```erlang
    │ │ │     1> arith:substract(arith:add(2, 3), 1).
    │ │ │ @@ -203,94 +203,94 @@
    │ │ │  

    There are three reserved metadata keys for -moduledoc:

    • since - Shows in which version of the application the module was added. │ │ │ If this is added, all functions, types, and callbacks within will also receive │ │ │ the same since value unless specified in the metadata of the function, type │ │ │ or callback.
    • deprecated - Shows a text in the documentation explaining that it is │ │ │ deprecated and what to use instead.
    • format - The format to use for all documentation in this module. The │ │ │ default is text/markdown. It should be written using the │ │ │ mime type │ │ │ -of the format.

    Example:

    -moduledoc {file, "../doc/arith.asciidoc"}.
    │ │ │ --moduledoc #{since => "0.1", format => "text/asciidoc"}.
    │ │ │ --moduledoc #{deprecated => "Use the Erlang arithmetic operators instead."}.

    │ │ │ +of the format.

    Example:

    -moduledoc {file, "../doc/arith.asciidoc"}.
    │ │ │ +-moduledoc #{since => "0.1", format => "text/asciidoc"}.
    │ │ │ +-moduledoc #{deprecated => "Use the Erlang arithmetic operators instead."}.

    │ │ │ │ │ │ │ │ │ │ │ │ Documenting functions, user-defined types, and callbacks │ │ │

    │ │ │

    Functions, types, and callbacks can be documented using the -doc attribute. │ │ │ Each entry should start with a short paragraph describing the purpose of entity, │ │ │ and then go into greater detail in needed.

    It is not recommended to include images or diagrams in this documentation as it │ │ │ is used by IDEs and c:h/1 to show the documentation to the user.

    For example:

    -doc """
    │ │ │  A number that can be used by the arith module.
    │ │ │  
    │ │ │  We use a special number here so that we know
    │ │ │  that this number comes from this module.
    │ │ │  """.
    │ │ │ --opaque number() :: {arith, erlang:number()}.
    │ │ │ +-opaque number() :: {arith, erlang:number()}.
    │ │ │  
    │ │ │  -doc """
    │ │ │  Adds two numbers.
    │ │ │  
    │ │ │  ### Example:
    │ │ │  
    │ │ │  ```
    │ │ │  1> arith:add(arith:number(1), arith:number(2)). {number, 3}
    │ │ │  ```
    │ │ │  """.
    │ │ │ --spec add(number(), number()) -> number().
    │ │ │ -add({number, One}, {number, Two}) -> {number, One + Two}.

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

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

    │ │ │

    There are four reserved metadata keys for -doc:

    • since => unicode:chardata() - Shows which version of the application the │ │ │ module was added.

    • deprecated => unicode:chardata() - Shows a text in the documentation │ │ │ explaining that it is deprecated and what to use instead. The compiler will │ │ │ automatically insert this key if there is a -deprecated attribute marking a │ │ │ function as deprecated.

    • equiv => unicode:chardata() | F/A | F(...) - Notes that this function is equivalent to │ │ │ another function in this module. The equivalence can be described using either │ │ │ -Func/Arity, Func(Args) or a unicode string. For example:

      -doc #{equiv => add/3}.
      │ │ │ -add(One, Two) -> add(One, Two, []).
      │ │ │ -add(One, Two, Options) -> ...

      or

      -doc #{equiv => add(One, Two, [])}.
      │ │ │ --spec add(One :: number(), Two :: number()) -> number().
      │ │ │ -add(One, Two) -> add(One, Two, []).
      │ │ │ -add(One, Two, Options) -> ...

      The entry into the EEP-48 doc chunk metadata is │ │ │ +Func/Arity, Func(Args) or a unicode string. For example:

      -doc #{equiv => add/3}.
      │ │ │ +add(One, Two) -> add(One, Two, []).
      │ │ │ +add(One, Two, Options) -> ...

      or

      -doc #{equiv => add(One, Two, [])}.
      │ │ │ +-spec add(One :: number(), Two :: number()) -> number().
      │ │ │ +add(One, Two) -> add(One, Two, []).
      │ │ │ +add(One, Two, Options) -> ...

      The entry into the EEP-48 doc chunk metadata is │ │ │ the value converted to a string.

    • exported => boolean() - A boolean/0 signifying if the entry is exported │ │ │ or not. This value is automatically set by the compiler and should not be set │ │ │ by the user.

    │ │ │ │ │ │ │ │ │ │ │ │ Doc signatures │ │ │

    │ │ │

    The doc signature is a short text shown to describe the function and its arguments. │ │ │ By default it is determined by looking at the names of the arguments in the │ │ │ --spec or function. For example:

    add(One, Two) -> One + Two.
    │ │ │ +-spec or function. For example:

    add(One, Two) -> One + Two.
    │ │ │  
    │ │ │ --spec sub(One :: integer(), Two :: integer()) -> integer().
    │ │ │ -sub(X, Y) -> X - Y.

    will have a signature of add(One, Two) and sub(One, Two).

    For types or callbacks, the signature is derived from the type or callback │ │ │ -specification. For example:

    -type number(Value) :: {number, Value}.
    │ │ │ +-spec sub(One :: integer(), Two :: integer()) -> integer().
    │ │ │ +sub(X, Y) -> X - Y.

    will have a signature of add(One, Two) and sub(One, Two).

    For types or callbacks, the signature is derived from the type or callback │ │ │ +specification. For example:

    -type number(Value) :: {number, Value}.
    │ │ │  %% signature will be `number(Value)`
    │ │ │  
    │ │ │ --opaque number() :: {number, number()}.
    │ │ │ +-opaque number() :: {number, number()}.
    │ │ │  %% signature will be `number()`
    │ │ │  
    │ │ │ --callback increment(In :: number()) -> Out.
    │ │ │ +-callback increment(In :: number()) -> Out.
    │ │ │  %% signature will be `increment(In)`
    │ │ │  
    │ │ │ --callback increment(In) -> Out when In :: number().
    │ │ │ +-callback increment(In) -> Out when In :: number().
    │ │ │  %% signature will be `increment(In)`

    If it is not possible to "easily" figure out a nice signature from the code, the │ │ │ MFA syntax is used instead. For example: add/2, number/1, increment/1

    It is possible to supply a custom signature by placing it as the first line of the │ │ │ -doc attribute. The provided signature must be in the form of a function │ │ │ declaration up until the ->. For example:

    -doc """
    │ │ │  add(One, Two)
    │ │ │  
    │ │ │  Adds two numbers.
    │ │ │  """.
    │ │ │ -add(A, B) -> A + B.

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

    Will create the signature add(One, Two). The signature will be removed from the │ │ │ documentation string, so in the example above only the text "Adds two numbers" │ │ │ will be part of the documentation. This works for functions, types, and │ │ │ callbacks.

    │ │ │ │ │ │ │ │ │ │ │ │ Compiling and getting documentation │ │ │ @@ -375,21 +375,21 @@ │ │ │ Using ExDoc to generate HTML/ePub documentation │ │ │

    │ │ │

    ExDoc has built-in support to generate │ │ │ documentation from Markdown. The simplest way is by using the │ │ │ rebar3_ex_doc plugin. To set up a │ │ │ rebar3 project to use ExDoc to generate │ │ │ documentation add the following to your rebar3.config.

    %% Enable the plugin
    │ │ │ -{plugins, [rebar3_ex_doc]}.
    │ │ │ +{plugins, [rebar3_ex_doc]}.
    │ │ │  
    │ │ │ -{ex_doc, [
    │ │ │ -  {extras, ["README.md"]},
    │ │ │ -  {main, "README.md"},
    │ │ │ -  {source_url, "https://github.com/namespace/your_app"}
    │ │ │ -]}.

    When configured you can run rebar3 ex_doc to generate the │ │ │ +{ex_doc, [ │ │ │ + {extras, ["README.md"]}, │ │ │ + {main, "README.md"}, │ │ │ + {source_url, "https://github.com/namespace/your_app"} │ │ │ +]}.

    When configured you can run rebar3 ex_doc to generate the │ │ │ documentation to doc/index.html. For more details and options see │ │ │ the rebar3_ex_doc documentation.

    You can also download the │ │ │ release escript bundle from │ │ │ github and run it from the command line. The documentation for using the escript │ │ │ is found by running ex_doc --help.

    If you are writing documentation that will be using │ │ │ ExDoc to generate HTML/ePub it is highly │ │ │ recommended to read its documentation.

    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/drivers.html │ │ │ @@ -122,23 +122,23 @@ │ │ │ Drivers and Concurrency │ │ │ │ │ │

    The runtime system always takes a lock before running any code in a driver.

    By default, that lock is at the driver level, that is, if several ports have │ │ │ been opened to the same driver, only code for one port at the same time can be │ │ │ running.

    A driver can be configured to have one lock for each port instead.

    If a driver is used in a functional way (that is, holds no state, but only does │ │ │ some heavy calculation and returns a result), several ports with registered │ │ │ names can be opened beforehand, and the port to be used can be chosen based on │ │ │ -the scheduler ID as follows:

    -define(PORT_NAMES(),
    │ │ │ -	{some_driver_01, some_driver_02, some_driver_03, some_driver_04,
    │ │ │ +the scheduler ID as follows:

    -define(PORT_NAMES(),
    │ │ │ +	{some_driver_01, some_driver_02, some_driver_03, some_driver_04,
    │ │ │  	 some_driver_05, some_driver_06, some_driver_07, some_driver_08,
    │ │ │  	 some_driver_09, some_driver_10, some_driver_11, some_driver_12,
    │ │ │ -	 some_driver_13, some_driver_14, some_driver_15, some_driver_16}).
    │ │ │ +	 some_driver_13, some_driver_14, some_driver_15, some_driver_16}).
    │ │ │  
    │ │ │ -client_port() ->
    │ │ │ -    element(erlang:system_info(scheduler_id) rem tuple_size(?PORT_NAMES()) + 1,
    │ │ │ -	    ?PORT_NAMES()).

    As long as there are no more than 16 schedulers, there will never be any lock │ │ │ +client_port() -> │ │ │ + element(erlang:system_info(scheduler_id) rem tuple_size(?PORT_NAMES()) + 1, │ │ │ + ?PORT_NAMES()).

    As long as there are no more than 16 schedulers, there will never be any lock │ │ │ contention on the port lock for the driver.

    │ │ │ │ │ │ │ │ │ │ │ │ Avoiding Copying Binaries When Calling a Driver │ │ │

    │ │ │

    There are basically two ways to avoid copying a binary that is sent to a driver:

    • If the Data argument for port_control/3 is a │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/eff_guide_functions.html │ │ │ @@ -122,67 +122,67 @@ │ │ │ Pattern Matching │ │ │ │ │ │

      Pattern matching in function head as well as in case and receive clauses are │ │ │ optimized by the compiler. With a few exceptions, there is nothing to gain by │ │ │ rearranging clauses.

      One exception is pattern matching of binaries. The compiler does not rearrange │ │ │ clauses that match binaries. Placing the clause that matches against the empty │ │ │ binary last is usually slightly faster than placing it first.

      The following is a rather unnatural example to show another exception where │ │ │ -rearranging clauses is beneficial:

      DO NOT

      atom_map1(one) -> 1;
      │ │ │ -atom_map1(two) -> 2;
      │ │ │ -atom_map1(three) -> 3;
      │ │ │ -atom_map1(Int) when is_integer(Int) -> Int;
      │ │ │ -atom_map1(four) -> 4;
      │ │ │ -atom_map1(five) -> 5;
      │ │ │ -atom_map1(six) -> 6.

      The problem is the clause with the variable Int. As a variable can match │ │ │ +rearranging clauses is beneficial:

      DO NOT

      atom_map1(one) -> 1;
      │ │ │ +atom_map1(two) -> 2;
      │ │ │ +atom_map1(three) -> 3;
      │ │ │ +atom_map1(Int) when is_integer(Int) -> Int;
      │ │ │ +atom_map1(four) -> 4;
      │ │ │ +atom_map1(five) -> 5;
      │ │ │ +atom_map1(six) -> 6.

      The problem is the clause with the variable Int. As a variable can match │ │ │ anything, including the atoms four, five, and six, which the following │ │ │ clauses also match, the compiler must generate suboptimal code that executes as │ │ │ follows:

      • First, the input value is compared to one, two, and three (using a │ │ │ single instruction that does a binary search; thus, quite efficient even if │ │ │ there are many values) to select which one of the first three clauses to │ │ │ execute (if any).
      • If none of the first three clauses match, the fourth clause match as a │ │ │ variable always matches.
      • If the guard test is_integer(Int) succeeds, the fourth │ │ │ clause is executed.
      • If the guard test fails, the input value is compared to four, five, and │ │ │ six, and the appropriate clause is selected. (There is a function_clause │ │ │ -exception if none of the values matched.)

      Rewriting to either:

      DO

      atom_map2(one) -> 1;
      │ │ │ -atom_map2(two) -> 2;
      │ │ │ -atom_map2(three) -> 3;
      │ │ │ -atom_map2(four) -> 4;
      │ │ │ -atom_map2(five) -> 5;
      │ │ │ -atom_map2(six) -> 6;
      │ │ │ -atom_map2(Int) when is_integer(Int) -> Int.

      or:

      DO

      atom_map3(Int) when is_integer(Int) -> Int;
      │ │ │ -atom_map3(one) -> 1;
      │ │ │ -atom_map3(two) -> 2;
      │ │ │ -atom_map3(three) -> 3;
      │ │ │ -atom_map3(four) -> 4;
      │ │ │ -atom_map3(five) -> 5;
      │ │ │ -atom_map3(six) -> 6.

      gives slightly more efficient matching code.

      Another example:

      DO NOT

      map_pairs1(_Map, [], Ys) ->
      │ │ │ +exception if none of the values matched.)

    Rewriting to either:

    DO

    atom_map2(one) -> 1;
    │ │ │ +atom_map2(two) -> 2;
    │ │ │ +atom_map2(three) -> 3;
    │ │ │ +atom_map2(four) -> 4;
    │ │ │ +atom_map2(five) -> 5;
    │ │ │ +atom_map2(six) -> 6;
    │ │ │ +atom_map2(Int) when is_integer(Int) -> Int.

    or:

    DO

    atom_map3(Int) when is_integer(Int) -> Int;
    │ │ │ +atom_map3(one) -> 1;
    │ │ │ +atom_map3(two) -> 2;
    │ │ │ +atom_map3(three) -> 3;
    │ │ │ +atom_map3(four) -> 4;
    │ │ │ +atom_map3(five) -> 5;
    │ │ │ +atom_map3(six) -> 6.

    gives slightly more efficient matching code.

    Another example:

    DO NOT

    map_pairs1(_Map, [], Ys) ->
    │ │ │      Ys;
    │ │ │ -map_pairs1(_Map, Xs, []) ->
    │ │ │ +map_pairs1(_Map, Xs, []) ->
    │ │ │      Xs;
    │ │ │ -map_pairs1(Map, [X|Xs], [Y|Ys]) ->
    │ │ │ -    [Map(X, Y)|map_pairs1(Map, Xs, Ys)].

    The first argument is not a problem. It is variable, but it is a variable in │ │ │ +map_pairs1(Map, [X|Xs], [Y|Ys]) -> │ │ │ + [Map(X, Y)|map_pairs1(Map, Xs, Ys)].

    The first argument is not a problem. It is variable, but it is a variable in │ │ │ all clauses. The problem is the variable in the second argument, Xs, in the │ │ │ middle clause. Because the variable can match anything, the compiler is not │ │ │ allowed to rearrange the clauses, but must generate code that matches them in │ │ │ the order written.

    If the function is rewritten as follows, the compiler is free to rearrange the │ │ │ -clauses:

    DO

    map_pairs2(_Map, [], Ys) ->
    │ │ │ +clauses:

    DO

    map_pairs2(_Map, [], Ys) ->
    │ │ │      Ys;
    │ │ │ -map_pairs2(_Map, [_|_]=Xs, [] ) ->
    │ │ │ +map_pairs2(_Map, [_|_]=Xs, [] ) ->
    │ │ │      Xs;
    │ │ │ -map_pairs2(Map, [X|Xs], [Y|Ys]) ->
    │ │ │ -    [Map(X, Y)|map_pairs2(Map, Xs, Ys)].

    The compiler will generate code similar to this:

    DO NOT (already done by the compiler)

    explicit_map_pairs(Map, Xs0, Ys0) ->
    │ │ │ +map_pairs2(Map, [X|Xs], [Y|Ys]) ->
    │ │ │ +    [Map(X, Y)|map_pairs2(Map, Xs, Ys)].

    The compiler will generate code similar to this:

    DO NOT (already done by the compiler)

    explicit_map_pairs(Map, Xs0, Ys0) ->
    │ │ │      case Xs0 of
    │ │ │ -	[X|Xs] ->
    │ │ │ +	[X|Xs] ->
    │ │ │  	    case Ys0 of
    │ │ │ -		[Y|Ys] ->
    │ │ │ -		    [Map(X, Y)|explicit_map_pairs(Map, Xs, Ys)];
    │ │ │ -		[] ->
    │ │ │ +		[Y|Ys] ->
    │ │ │ +		    [Map(X, Y)|explicit_map_pairs(Map, Xs, Ys)];
    │ │ │ +		[] ->
    │ │ │  		    Xs0
    │ │ │  	    end;
    │ │ │ -	[] ->
    │ │ │ +	[] ->
    │ │ │  	    Ys0
    │ │ │      end.

    This is slightly faster for probably the most common case that the input lists │ │ │ are not empty or very short. (Another advantage is that Dialyzer can deduce a │ │ │ better type for the Xs variable.)

    │ │ │ │ │ │ │ │ │ │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/eff_guide_processes.html │ │ │ @@ -119,45 +119,45 @@ │ │ │ │ │ │ │ │ │ │ │ │ Creating an Erlang Process │ │ │

    │ │ │

    An Erlang process is lightweight compared to threads and processes in operating │ │ │ systems.

    A newly spawned Erlang process uses 327 words of memory. The size can be found │ │ │ -as follows:

    Erlang/OTP 27 [erts-14.2.3] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
    │ │ │ +as follows:

    Erlang/OTP 27 [erts-14.2.3] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:1] [jit]
    │ │ │  
    │ │ │ -Eshell V14.2.3 (press Ctrl+G to abort, type help(). for help)
    │ │ │ -1> Fun = fun() -> receive after infinity -> ok end end.
    │ │ │ +Eshell V14.2.3 (press Ctrl+G to abort, type help(). for help)
    │ │ │ +1> Fun = fun() -> receive after infinity -> ok end end.
    │ │ │  #Fun<erl_eval.43.39164016>
    │ │ │ -2> {_,Bytes} = process_info(spawn(Fun), memory).
    │ │ │ -{memory,2616}
    │ │ │ -3> Bytes div erlang:system_info(wordsize).
    │ │ │ +2> {_,Bytes} = process_info(spawn(Fun), memory).
    │ │ │ +{memory,2616}
    │ │ │ +3> Bytes div erlang:system_info(wordsize).
    │ │ │  327

    The size includes 233 words for the heap area (which includes the stack). The │ │ │ garbage collector increases the heap as needed.

    The main (outer) loop for a process must be tail-recursive. Otherwise, the │ │ │ -stack grows until the process terminates.

    DO NOT

    loop() ->
    │ │ │ +stack grows until the process terminates.

    DO NOT

    loop() ->
    │ │ │    receive
    │ │ │ -     {sys, Msg} ->
    │ │ │ -         handle_sys_msg(Msg),
    │ │ │ -         loop();
    │ │ │ -     {From, Msg} ->
    │ │ │ -          Reply = handle_msg(Msg),
    │ │ │ +     {sys, Msg} ->
    │ │ │ +         handle_sys_msg(Msg),
    │ │ │ +         loop();
    │ │ │ +     {From, Msg} ->
    │ │ │ +          Reply = handle_msg(Msg),
    │ │ │            From ! Reply,
    │ │ │ -          loop()
    │ │ │ +          loop()
    │ │ │    end,
    │ │ │ -  io:format("Message is processed~n", []).

    The call to io:format/2 will never be executed, but a return address will │ │ │ + io:format("Message is processed~n", []).

    The call to io:format/2 will never be executed, but a return address will │ │ │ still be pushed to the stack each time loop/0 is called recursively. The │ │ │ -correct tail-recursive version of the function looks as follows:

    DO

    loop() ->
    │ │ │ +correct tail-recursive version of the function looks as follows:

    DO

    loop() ->
    │ │ │     receive
    │ │ │ -      {sys, Msg} ->
    │ │ │ -         handle_sys_msg(Msg),
    │ │ │ -         loop();
    │ │ │ -      {From, Msg} ->
    │ │ │ -         Reply = handle_msg(Msg),
    │ │ │ +      {sys, Msg} ->
    │ │ │ +         handle_sys_msg(Msg),
    │ │ │ +         loop();
    │ │ │ +      {From, Msg} ->
    │ │ │ +         Reply = handle_msg(Msg),
    │ │ │           From ! Reply,
    │ │ │ -         loop()
    │ │ │ +         loop()
    │ │ │   end.

    │ │ │ │ │ │ │ │ │ │ │ │ Initial Heap Size │ │ │

    │ │ │

    The default initial heap size of 233 words is quite conservative to support │ │ │ @@ -189,30 +189,30 @@ │ │ │ │ │ │ │ │ │ Receiving messages │ │ │ │ │ │

    The cost of receiving messages depends on how complicated the receive │ │ │ expression is. A simple expression that matches any message is very cheap │ │ │ because it retrieves the first message in the message queue:

    DO

    receive
    │ │ │ -    Message -> handle_msg(Message)
    │ │ │ +    Message -> handle_msg(Message)
    │ │ │  end.

    However, this is not always convenient: we can receive a message that we do not │ │ │ know how to handle at this point, so it is common to only match the messages we │ │ │ expect:

    receive
    │ │ │ -    {Tag, Message} -> handle_msg(Message)
    │ │ │ +    {Tag, Message} -> handle_msg(Message)
    │ │ │  end.

    While this is convenient it means that the entire message queue must be searched │ │ │ until it finds a matching message. This is very expensive for processes with │ │ │ long message queues, so there is an optimization for the common case of │ │ │ -sending a request and waiting for a response shortly after:

    DO

    MRef = monitor(process, Process),
    │ │ │ -Process ! {self(), MRef, Request},
    │ │ │ +sending a request and waiting for a response shortly after:

    DO

    MRef = monitor(process, Process),
    │ │ │ +Process ! {self(), MRef, Request},
    │ │ │  receive
    │ │ │ -    {MRef, Reply} ->
    │ │ │ -        erlang:demonitor(MRef, [flush]),
    │ │ │ -        handle_reply(Reply);
    │ │ │ -    {'DOWN', MRef, _, _, Reason} ->
    │ │ │ -        handle_error(Reason)
    │ │ │ +    {MRef, Reply} ->
    │ │ │ +        erlang:demonitor(MRef, [flush]),
    │ │ │ +        handle_reply(Reply);
    │ │ │ +    {'DOWN', MRef, _, _, Reason} ->
    │ │ │ +        handle_error(Reason)
    │ │ │  end.

    Since the compiler knows that the reference created by │ │ │ monitor/2 cannot exist before the call (since it is a globally │ │ │ unique identifier), and that the receive only matches messages that contain │ │ │ said reference, it will tell the emulator to search only the messages that │ │ │ arrived after the call to monitor/2.

    The above is a simple example where one is but guaranteed that the optimization │ │ │ will take, but what about more complicated code?

    │ │ │ │ │ │ @@ -228,101 +228,101 @@ │ │ │ efficiency_guide.erl:200: Warning: NOT OPTIMIZED: all clauses do not match a suitable reference │ │ │ efficiency_guide.erl:206: Warning: OPTIMIZED: reference used to mark a message queue position │ │ │ efficiency_guide.erl:208: Warning: OPTIMIZED: all clauses match reference created by monitor/2 at efficiency_guide.erl:206 │ │ │ efficiency_guide.erl:219: Warning: INFO: passing reference created by make_ref/0 at efficiency_guide.erl:218 │ │ │ efficiency_guide.erl:222: Warning: OPTIMIZED: all clauses match reference in function parameter 1

    To make it clearer exactly what code the warnings refer to, the warnings in the │ │ │ following examples are inserted as comments after the clause they refer to, for │ │ │ example:

    %% DO
    │ │ │ -simple_receive() ->
    │ │ │ +simple_receive() ->
    │ │ │  %% efficiency_guide.erl:194: Warning: INFO: not a selective receive, this is always fast
    │ │ │  receive
    │ │ │ -    Message -> handle_msg(Message)
    │ │ │ +    Message -> handle_msg(Message)
    │ │ │  end.
    │ │ │  
    │ │ │  %% DO NOT, unless Tag is known to be a suitable reference: see
    │ │ │  %% cross_function_receive/0 further down.
    │ │ │ -selective_receive(Tag, Message) ->
    │ │ │ +selective_receive(Tag, Message) ->
    │ │ │  %% efficiency_guide.erl:200: Warning: NOT OPTIMIZED: all clauses do not match a suitable reference
    │ │ │  receive
    │ │ │ -    {Tag, Message} -> handle_msg(Message)
    │ │ │ +    {Tag, Message} -> handle_msg(Message)
    │ │ │  end.
    │ │ │  
    │ │ │  %% DO
    │ │ │ -optimized_receive(Process, Request) ->
    │ │ │ +optimized_receive(Process, Request) ->
    │ │ │  %% efficiency_guide.erl:206: Warning: OPTIMIZED: reference used to mark a message queue position
    │ │ │ -    MRef = monitor(process, Process),
    │ │ │ -    Process ! {self(), MRef, Request},
    │ │ │ +    MRef = monitor(process, Process),
    │ │ │ +    Process ! {self(), MRef, Request},
    │ │ │      %% efficiency_guide.erl:208: Warning: OPTIMIZED: matches reference created by monitor/2 at efficiency_guide.erl:206
    │ │ │      receive
    │ │ │ -        {MRef, Reply} ->
    │ │ │ -        erlang:demonitor(MRef, [flush]),
    │ │ │ -        handle_reply(Reply);
    │ │ │ -    {'DOWN', MRef, _, _, Reason} ->
    │ │ │ -    handle_error(Reason)
    │ │ │ +        {MRef, Reply} ->
    │ │ │ +        erlang:demonitor(MRef, [flush]),
    │ │ │ +        handle_reply(Reply);
    │ │ │ +    {'DOWN', MRef, _, _, Reason} ->
    │ │ │ +    handle_error(Reason)
    │ │ │      end.
    │ │ │  
    │ │ │  %% DO
    │ │ │ -cross_function_receive() ->
    │ │ │ +cross_function_receive() ->
    │ │ │      %% efficiency_guide.erl:218: Warning: OPTIMIZED: reference used to mark a message queue position
    │ │ │ -    Ref = make_ref(),
    │ │ │ +    Ref = make_ref(),
    │ │ │      %% efficiency_guide.erl:219: Warning: INFO: passing reference created by make_ref/0 at efficiency_guide.erl:218
    │ │ │ -    cross_function_receive(Ref).
    │ │ │ +    cross_function_receive(Ref).
    │ │ │  
    │ │ │ -cross_function_receive(Ref) ->
    │ │ │ +cross_function_receive(Ref) ->
    │ │ │      %% efficiency_guide.erl:222: Warning: OPTIMIZED: all clauses match reference in function parameter 1
    │ │ │      receive
    │ │ │ -        {Ref, Message} -> handle_msg(Message)
    │ │ │ +        {Ref, Message} -> handle_msg(Message)
    │ │ │      end.

    │ │ │ │ │ │ │ │ │ │ │ │ Literal Pool │ │ │

    │ │ │

    Constant Erlang terms (hereafter called literals) are kept in literal pools; │ │ │ each loaded module has its own pool. The following function does not build the │ │ │ tuple every time it is called (only to have it discarded the next time the │ │ │ garbage collector was run), but the tuple is located in the module's literal │ │ │ -pool:

    DO

    days_in_month(M) ->
    │ │ │ -    element(M, {31,28,31,30,31,30,31,31,30,31,30,31}).

    If a literal, or a term that contains a literal, is inserted into an Ets table, │ │ │ +pool:

    DO

    days_in_month(M) ->
    │ │ │ +    element(M, {31,28,31,30,31,30,31,31,30,31,30,31}).

    If a literal, or a term that contains a literal, is inserted into an Ets table, │ │ │ it is copied. The reason is that the module containing the literal can be │ │ │ unloaded in the future.

    When a literal is sent to another process, it is not copied. When a module │ │ │ holding a literal is unloaded, the literal will be copied to the heap of all │ │ │ processes that hold references to that literal.

    There also exists a global literal pool that is managed by the │ │ │ persistent_term module.

    By default, 1 GB of virtual address space is reserved for all literal pools (in │ │ │ BEAM code and persistent terms). The amount of virtual address space reserved │ │ │ for literals can be changed by using the │ │ │ +MIscs option when starting the emulator.

    Here is an example how the reserved virtual address space for literals can be │ │ │ raised to 2 GB (2048 MB):

    erl +MIscs 2048

    │ │ │ │ │ │ │ │ │ │ │ │ Loss of Sharing │ │ │

    │ │ │ -

    An Erlang term can have shared subterms. Here is a simple example:

    {SubTerm, SubTerm}

    Shared subterms are not preserved in the following cases:

    • When a term is sent to another process
    • When a term is passed as the initial process arguments in the spawn call
    • When a term is stored in an Ets table

    That is an optimization. Most applications do not send messages with shared │ │ │ -subterms.

    The following example shows how a shared subterm can be created:

    kilo_byte() ->
    │ │ │ -    kilo_byte(10, [42]).
    │ │ │ +

    An Erlang term can have shared subterms. Here is a simple example:

    {SubTerm, SubTerm}

    Shared subterms are not preserved in the following cases:

    • When a term is sent to another process
    • When a term is passed as the initial process arguments in the spawn call
    • When a term is stored in an Ets table

    That is an optimization. Most applications do not send messages with shared │ │ │ +subterms.

    The following example shows how a shared subterm can be created:

    kilo_byte() ->
    │ │ │ +    kilo_byte(10, [42]).
    │ │ │  
    │ │ │ -kilo_byte(0, Acc) ->
    │ │ │ +kilo_byte(0, Acc) ->
    │ │ │      Acc;
    │ │ │ -kilo_byte(N, Acc) ->
    │ │ │ -    kilo_byte(N-1, [Acc|Acc]).

    kilo_byte/1 creates a deep list. If list_to_binary/1 │ │ │ +kilo_byte(N, Acc) -> │ │ │ + kilo_byte(N-1, [Acc|Acc]).

    kilo_byte/1 creates a deep list. If list_to_binary/1 │ │ │ is called, the deep list can be converted to a binary of 1024 bytes:

    1> byte_size(list_to_binary(efficiency_guide:kilo_byte())).
    │ │ │  1024

    Using the erts_debug:size/1 BIF, it can be seen that the deep list only │ │ │ -requires 22 words of heap space:

    2> erts_debug:size(efficiency_guide:kilo_byte()).
    │ │ │ +requires 22 words of heap space:

    2> erts_debug:size(efficiency_guide:kilo_byte()).
    │ │ │  22

    Using the erts_debug:flat_size/1 BIF, the size of the deep list can be │ │ │ calculated if sharing is ignored. It becomes the size of the list when it has │ │ │ -been sent to another process or stored in an Ets table:

    3> erts_debug:flat_size(efficiency_guide:kilo_byte()).
    │ │ │ +been sent to another process or stored in an Ets table:

    3> erts_debug:flat_size(efficiency_guide:kilo_byte()).
    │ │ │  4094

    It can be verified that sharing will be lost if the data is inserted into an Ets │ │ │ -table:

    4> T = ets:new(tab, []).
    │ │ │ +table:

    4> T = ets:new(tab, []).
    │ │ │  #Ref<0.1662103692.2407923716.214181>
    │ │ │ -5> ets:insert(T, {key,efficiency_guide:kilo_byte()}).
    │ │ │ +5> ets:insert(T, {key,efficiency_guide:kilo_byte()}).
    │ │ │  true
    │ │ │ -6> erts_debug:size(element(2, hd(ets:lookup(T, key)))).
    │ │ │ +6> erts_debug:size(element(2, hd(ets:lookup(T, key)))).
    │ │ │  4094
    │ │ │ -7> erts_debug:flat_size(element(2, hd(ets:lookup(T, key)))).
    │ │ │ +7> erts_debug:flat_size(element(2, hd(ets:lookup(T, key)))).
    │ │ │  4094

    When the data has passed through an Ets table, erts_debug:size/1 and │ │ │ erts_debug:flat_size/1 return the same value. Sharing has been lost.

    It is possible to build an experimental variant of the runtime system that │ │ │ will preserve sharing when copying terms by giving the │ │ │ --enable-sharing-preserving option to the configure script.

    │ │ │ │ │ │ │ │ │ │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/erl_interface.html │ │ │ @@ -120,119 +120,119 @@ │ │ │ to read the port example in Ports before reading this section.

    │ │ │ │ │ │ │ │ │ │ │ │ Erlang Program │ │ │

    │ │ │

    The following example shows an Erlang program communicating with a C program │ │ │ -over a plain port with home made encoding:

    -module(complex1).
    │ │ │ --export([start/1, stop/0, init/1]).
    │ │ │ --export([foo/1, bar/1]).
    │ │ │ -
    │ │ │ -start(ExtPrg) ->
    │ │ │ -    spawn(?MODULE, init, [ExtPrg]).
    │ │ │ -stop() ->
    │ │ │ +over a plain port with home made encoding:

    -module(complex1).
    │ │ │ +-export([start/1, stop/0, init/1]).
    │ │ │ +-export([foo/1, bar/1]).
    │ │ │ +
    │ │ │ +start(ExtPrg) ->
    │ │ │ +    spawn(?MODULE, init, [ExtPrg]).
    │ │ │ +stop() ->
    │ │ │      complex ! stop.
    │ │ │  
    │ │ │ -foo(X) ->
    │ │ │ -    call_port({foo, X}).
    │ │ │ -bar(Y) ->
    │ │ │ -    call_port({bar, Y}).
    │ │ │ +foo(X) ->
    │ │ │ +    call_port({foo, X}).
    │ │ │ +bar(Y) ->
    │ │ │ +    call_port({bar, Y}).
    │ │ │  
    │ │ │ -call_port(Msg) ->
    │ │ │ -    complex ! {call, self(), Msg},
    │ │ │ +call_port(Msg) ->
    │ │ │ +    complex ! {call, self(), Msg},
    │ │ │      receive
    │ │ │ -	{complex, Result} ->
    │ │ │ +	{complex, Result} ->
    │ │ │  	    Result
    │ │ │      end.
    │ │ │  
    │ │ │ -init(ExtPrg) ->
    │ │ │ -    register(complex, self()),
    │ │ │ -    process_flag(trap_exit, true),
    │ │ │ -    Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
    │ │ │ -    loop(Port).
    │ │ │ +init(ExtPrg) ->
    │ │ │ +    register(complex, self()),
    │ │ │ +    process_flag(trap_exit, true),
    │ │ │ +    Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
    │ │ │ +    loop(Port).
    │ │ │  
    │ │ │ -loop(Port) ->
    │ │ │ +loop(Port) ->
    │ │ │      receive
    │ │ │ -	{call, Caller, Msg} ->
    │ │ │ -	    Port ! {self(), {command, encode(Msg)}},
    │ │ │ +	{call, Caller, Msg} ->
    │ │ │ +	    Port ! {self(), {command, encode(Msg)}},
    │ │ │  	    receive
    │ │ │ -		{Port, {data, Data}} ->
    │ │ │ -		    Caller ! {complex, decode(Data)}
    │ │ │ +		{Port, {data, Data}} ->
    │ │ │ +		    Caller ! {complex, decode(Data)}
    │ │ │  	    end,
    │ │ │ -	    loop(Port);
    │ │ │ +	    loop(Port);
    │ │ │  	stop ->
    │ │ │ -	    Port ! {self(), close},
    │ │ │ +	    Port ! {self(), close},
    │ │ │  	    receive
    │ │ │ -		{Port, closed} ->
    │ │ │ -		    exit(normal)
    │ │ │ +		{Port, closed} ->
    │ │ │ +		    exit(normal)
    │ │ │  	    end;
    │ │ │ -	{'EXIT', Port, Reason} ->
    │ │ │ -	    exit(port_terminated)
    │ │ │ +	{'EXIT', Port, Reason} ->
    │ │ │ +	    exit(port_terminated)
    │ │ │      end.
    │ │ │  
    │ │ │ -encode({foo, X}) -> [1, X];
    │ │ │ -encode({bar, Y}) -> [2, Y].
    │ │ │ +encode({foo, X}) -> [1, X];
    │ │ │ +encode({bar, Y}) -> [2, Y].
    │ │ │  
    │ │ │ -decode([Int]) -> Int.

    There are two differences when using Erl_Interface on the C side compared to the │ │ │ +decode([Int]) -> Int.

    There are two differences when using Erl_Interface on the C side compared to the │ │ │ example in Ports, using only the plain port:

    • As Erl_Interface operates on the Erlang external term format, the port must be │ │ │ set to use binaries.
    • Instead of inventing an encoding/decoding scheme, the │ │ │ term_to_binary/1 and │ │ │ -binary_to_term/1 BIFs are to be used.

    That is:

    open_port({spawn, ExtPrg}, [{packet, 2}])

    is replaced with:

    open_port({spawn, ExtPrg}, [{packet, 2}, binary])

    And:

    Port ! {self(), {command, encode(Msg)}},
    │ │ │ +binary_to_term/1 BIFs are to be used.

    That is:

    open_port({spawn, ExtPrg}, [{packet, 2}])

    is replaced with:

    open_port({spawn, ExtPrg}, [{packet, 2}, binary])

    And:

    Port ! {self(), {command, encode(Msg)}},
    │ │ │  receive
    │ │ │ -  {Port, {data, Data}} ->
    │ │ │ -    Caller ! {complex, decode(Data)}
    │ │ │ -end

    is replaced with:

    Port ! {self(), {command, term_to_binary(Msg)}},
    │ │ │ +  {Port, {data, Data}} ->
    │ │ │ +    Caller ! {complex, decode(Data)}
    │ │ │ +end

    is replaced with:

    Port ! {self(), {command, term_to_binary(Msg)}},
    │ │ │  receive
    │ │ │ -  {Port, {data, Data}} ->
    │ │ │ -    Caller ! {complex, binary_to_term(Data)}
    │ │ │ -end

    The resulting Erlang program is as follows:

    -module(complex2).
    │ │ │ --export([start/1, stop/0, init/1]).
    │ │ │ --export([foo/1, bar/1]).
    │ │ │ -
    │ │ │ -start(ExtPrg) ->
    │ │ │ -    spawn(?MODULE, init, [ExtPrg]).
    │ │ │ -stop() ->
    │ │ │ +  {Port, {data, Data}} ->
    │ │ │ +    Caller ! {complex, binary_to_term(Data)}
    │ │ │ +end

    The resulting Erlang program is as follows:

    -module(complex2).
    │ │ │ +-export([start/1, stop/0, init/1]).
    │ │ │ +-export([foo/1, bar/1]).
    │ │ │ +
    │ │ │ +start(ExtPrg) ->
    │ │ │ +    spawn(?MODULE, init, [ExtPrg]).
    │ │ │ +stop() ->
    │ │ │      complex ! stop.
    │ │ │  
    │ │ │ -foo(X) ->
    │ │ │ -    call_port({foo, X}).
    │ │ │ -bar(Y) ->
    │ │ │ -    call_port({bar, Y}).
    │ │ │ +foo(X) ->
    │ │ │ +    call_port({foo, X}).
    │ │ │ +bar(Y) ->
    │ │ │ +    call_port({bar, Y}).
    │ │ │  
    │ │ │ -call_port(Msg) ->
    │ │ │ -    complex ! {call, self(), Msg},
    │ │ │ +call_port(Msg) ->
    │ │ │ +    complex ! {call, self(), Msg},
    │ │ │      receive
    │ │ │ -	{complex, Result} ->
    │ │ │ +	{complex, Result} ->
    │ │ │  	    Result
    │ │ │      end.
    │ │ │  
    │ │ │ -init(ExtPrg) ->
    │ │ │ -    register(complex, self()),
    │ │ │ -    process_flag(trap_exit, true),
    │ │ │ -    Port = open_port({spawn, ExtPrg}, [{packet, 2}, binary]),
    │ │ │ -    loop(Port).
    │ │ │ +init(ExtPrg) ->
    │ │ │ +    register(complex, self()),
    │ │ │ +    process_flag(trap_exit, true),
    │ │ │ +    Port = open_port({spawn, ExtPrg}, [{packet, 2}, binary]),
    │ │ │ +    loop(Port).
    │ │ │  
    │ │ │ -loop(Port) ->
    │ │ │ +loop(Port) ->
    │ │ │      receive
    │ │ │ -	{call, Caller, Msg} ->
    │ │ │ -	    Port ! {self(), {command, term_to_binary(Msg)}},
    │ │ │ +	{call, Caller, Msg} ->
    │ │ │ +	    Port ! {self(), {command, term_to_binary(Msg)}},
    │ │ │  	    receive
    │ │ │ -		{Port, {data, Data}} ->
    │ │ │ -		    Caller ! {complex, binary_to_term(Data)}
    │ │ │ +		{Port, {data, Data}} ->
    │ │ │ +		    Caller ! {complex, binary_to_term(Data)}
    │ │ │  	    end,
    │ │ │ -	    loop(Port);
    │ │ │ +	    loop(Port);
    │ │ │  	stop ->
    │ │ │ -	    Port ! {self(), close},
    │ │ │ +	    Port ! {self(), close},
    │ │ │  	    receive
    │ │ │ -		{Port, closed} ->
    │ │ │ -		    exit(normal)
    │ │ │ +		{Port, closed} ->
    │ │ │ +		    exit(normal)
    │ │ │  	    end;
    │ │ │ -	{'EXIT', Port, Reason} ->
    │ │ │ -	    exit(port_terminated)
    │ │ │ +	{'EXIT', Port, Reason} ->
    │ │ │ +	    exit(port_terminated)
    │ │ │      end.

    Notice that calling complex2:foo/1 and complex2:bar/1 results in the tuple │ │ │ {foo,X} or {bar,Y} being sent to the complex process, which codes them as │ │ │ binaries and sends them to the port. This means that the C program must be able │ │ │ to handle these two tuples.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -244,145 +244,145 @@ │ │ │ #include "ei.h" │ │ │ #include <unistd.h> │ │ │ #include <string.h> │ │ │ #include <stdlib.h> │ │ │ │ │ │ typedef unsigned char byte; │ │ │ │ │ │ -int read_cmd(byte *buf); │ │ │ -int write_cmd(byte *buf, int len); │ │ │ -int foo(int x); │ │ │ -int bar(int y); │ │ │ - │ │ │ -static void fail(int place) { │ │ │ - fprintf(stderr, "Something went wrong %d\n", place); │ │ │ - exit(1); │ │ │ -} │ │ │ +int read_cmd(byte *buf); │ │ │ +int write_cmd(byte *buf, int len); │ │ │ +int foo(int x); │ │ │ +int bar(int y); │ │ │ + │ │ │ +static void fail(int place) { │ │ │ + fprintf(stderr, "Something went wrong %d\n", place); │ │ │ + exit(1); │ │ │ +} │ │ │ │ │ │ -int main() { │ │ │ - byte buf[100]; │ │ │ +int main() { │ │ │ + byte buf[100]; │ │ │ int index = 0; │ │ │ int version = 0; │ │ │ int arity = 0; │ │ │ - char atom[128]; │ │ │ + char atom[128]; │ │ │ long in = 0; │ │ │ int res = 0; │ │ │ ei_x_buff res_buf; │ │ │ - ei_init(); │ │ │ - while (read_cmd(buf) > 0) { │ │ │ - if (ei_decode_version(buf, &index, &version) != 0) │ │ │ - fail(1); │ │ │ - if (ei_decode_tuple_header(buf, &index, &arity) != 0) │ │ │ - fail(2); │ │ │ - if (arity != 2) │ │ │ - fail(3); │ │ │ - if (ei_decode_atom(buf, &index, atom) != 0) │ │ │ - fail(4); │ │ │ - if (ei_decode_long(buf, &index, &in) != 0) │ │ │ - fail(5); │ │ │ - if (strncmp(atom, "foo", 3) == 0) { │ │ │ - res = foo((int)in); │ │ │ - } else if (strncmp(atom, "bar", 3) == 0) { │ │ │ - res = bar((int)in); │ │ │ - } │ │ │ - if (ei_x_new_with_version(&res_buf) != 0) │ │ │ - fail(6); │ │ │ - if (ei_x_encode_long(&res_buf, res) != 0) │ │ │ - fail(7); │ │ │ - write_cmd(res_buf.buff, res_buf.index); │ │ │ + ei_init(); │ │ │ + while (read_cmd(buf) > 0) { │ │ │ + if (ei_decode_version(buf, &index, &version) != 0) │ │ │ + fail(1); │ │ │ + if (ei_decode_tuple_header(buf, &index, &arity) != 0) │ │ │ + fail(2); │ │ │ + if (arity != 2) │ │ │ + fail(3); │ │ │ + if (ei_decode_atom(buf, &index, atom) != 0) │ │ │ + fail(4); │ │ │ + if (ei_decode_long(buf, &index, &in) != 0) │ │ │ + fail(5); │ │ │ + if (strncmp(atom, "foo", 3) == 0) { │ │ │ + res = foo((int)in); │ │ │ + } else if (strncmp(atom, "bar", 3) == 0) { │ │ │ + res = bar((int)in); │ │ │ + } │ │ │ + if (ei_x_new_with_version(&res_buf) != 0) │ │ │ + fail(6); │ │ │ + if (ei_x_encode_long(&res_buf, res) != 0) │ │ │ + fail(7); │ │ │ + write_cmd(res_buf.buff, res_buf.index); │ │ │ │ │ │ - if (ei_x_free(&res_buf) != 0) │ │ │ - fail(8); │ │ │ + if (ei_x_free(&res_buf) != 0) │ │ │ + fail(8); │ │ │ index = 0; │ │ │ - } │ │ │ -}

    The following functions, read_cmd() and write_cmd(), from the erl_comm.c │ │ │ + } │ │ │ +}

    The following functions, read_cmd() and write_cmd(), from the erl_comm.c │ │ │ example in Ports can still be used for reading from and writing to │ │ │ the port:

    /* erl_comm.c */
    │ │ │  
    │ │ │  #include <stdio.h>
    │ │ │  #include <unistd.h>
    │ │ │  
    │ │ │  typedef unsigned char byte;
    │ │ │  
    │ │ │ -int read_exact(byte *buf, int len)
    │ │ │ -{
    │ │ │ +int read_exact(byte *buf, int len)
    │ │ │ +{
    │ │ │    int i, got=0;
    │ │ │  
    │ │ │ -  do {
    │ │ │ -      if ((i = read(0, buf+got, len-got)) <= 0){
    │ │ │ -          return(i);
    │ │ │ -      }
    │ │ │ +  do {
    │ │ │ +      if ((i = read(0, buf+got, len-got)) <= 0){
    │ │ │ +          return(i);
    │ │ │ +      }
    │ │ │      got += i;
    │ │ │ -  } while (got<len);
    │ │ │ +  } while (got<len);
    │ │ │  
    │ │ │ -  return(len);
    │ │ │ -}
    │ │ │ +  return(len);
    │ │ │ +}
    │ │ │  
    │ │ │ -int write_exact(byte *buf, int len)
    │ │ │ -{
    │ │ │ +int write_exact(byte *buf, int len)
    │ │ │ +{
    │ │ │    int i, wrote = 0;
    │ │ │  
    │ │ │ -  do {
    │ │ │ -    if ((i = write(1, buf+wrote, len-wrote)) <= 0)
    │ │ │ -      return (i);
    │ │ │ +  do {
    │ │ │ +    if ((i = write(1, buf+wrote, len-wrote)) <= 0)
    │ │ │ +      return (i);
    │ │ │      wrote += i;
    │ │ │ -  } while (wrote<len);
    │ │ │ +  } while (wrote<len);
    │ │ │  
    │ │ │ -  return (len);
    │ │ │ -}
    │ │ │ +  return (len);
    │ │ │ +}
    │ │ │  
    │ │ │ -int read_cmd(byte *buf)
    │ │ │ -{
    │ │ │ +int read_cmd(byte *buf)
    │ │ │ +{
    │ │ │    int len;
    │ │ │  
    │ │ │ -  if (read_exact(buf, 2) != 2)
    │ │ │ -    return(-1);
    │ │ │ -  len = (buf[0] << 8) | buf[1];
    │ │ │ -  return read_exact(buf, len);
    │ │ │ -}
    │ │ │ +  if (read_exact(buf, 2) != 2)
    │ │ │ +    return(-1);
    │ │ │ +  len = (buf[0] << 8) | buf[1];
    │ │ │ +  return read_exact(buf, len);
    │ │ │ +}
    │ │ │  
    │ │ │ -int write_cmd(byte *buf, int len)
    │ │ │ -{
    │ │ │ +int write_cmd(byte *buf, int len)
    │ │ │ +{
    │ │ │    byte li;
    │ │ │  
    │ │ │ -  li = (len >> 8) & 0xff;
    │ │ │ -  write_exact(&li, 1);
    │ │ │ +  li = (len >> 8) & 0xff;
    │ │ │ +  write_exact(&li, 1);
    │ │ │  
    │ │ │    li = len & 0xff;
    │ │ │ -  write_exact(&li, 1);
    │ │ │ +  write_exact(&li, 1);
    │ │ │  
    │ │ │ -  return write_exact(buf, len);
    │ │ │ -}

    │ │ │ + return write_exact(buf, len); │ │ │ +}

    │ │ │ │ │ │ │ │ │ │ │ │ Running the Example │ │ │

    │ │ │

    Step 1. Compile the C code. This provides the paths to the include file │ │ │ ei.h, and also to the library ei:

    $ gcc -o extprg -I/usr/local/otp/lib/erl_interface-3.9.2/include \
    │ │ │      -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}.
    │ │ │
    │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │ │ │ │ Description │ │ │ │ │ │

    A common interoperability situation is when you want to incorporate a piece of │ │ │ code, solving a complex problem, in your Erlang program. Suppose for example, │ │ │ that you have the following C functions that you would like to call from Erlang:

    /* complex.c */
    │ │ │  
    │ │ │ -int foo(int x) {
    │ │ │ +int foo(int x) {
    │ │ │    return x+1;
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -int bar(int y) {
    │ │ │ +int bar(int y) {
    │ │ │    return y*2;
    │ │ │ -}

    The functions are deliberately kept as simple as possible, for readability │ │ │ +}

    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.

    │ │ │ │ │ │ │ │ │
    │ │ │
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/expressions.html │ │ │ @@ -151,81 +151,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,
    │ │ │ @@ -239,30 +239,30 @@
    │ │ │  can safely be skipped on a first reading.

    The = character is used to denote two similar but distinct operators: the │ │ │ match operator and the compound pattern operator. Which one is meant is │ │ │ determined by context.

    The compound pattern operator is used to construct a compound pattern from two │ │ │ patterns. Compound patterns are accepted everywhere a pattern is accepted. A │ │ │ compound pattern matches if all of its constituent patterns match. It is not │ │ │ legal for a pattern that is part of a compound pattern to use variables (as keys │ │ │ in map patterns or sizes in binary patterns) bound in other sub patterns of the │ │ │ -same compound pattern.

    Examples:

    1> fun(#{Key := Value} = #{key := Key}) -> Value end.
    │ │ │ +same compound pattern.

    Examples:

    1> fun(#{Key := Value} = #{key := Key}) -> Value end.
    │ │ │  * 1:7: variable 'Key' is unbound
    │ │ │ -2> F = fun({A, B} = E) -> {E, A + B} end, F({1,2}).
    │ │ │ -{{1,2},3}
    │ │ │ -3> G = fun(<<A:8,B:8>> = <<C:16>>) -> {A, B, C} end, G(<<42,43>>).
    │ │ │ -{42,43,10795}

    The match operator is allowed everywhere an expression is allowed. It is used │ │ │ +2> F = fun({A, B} = E) -> {E, A + B} end, F({1,2}). │ │ │ +{{1,2},3} │ │ │ +3> G = fun(<<A:8,B:8>> = <<C:16>>) -> {A, B, C} end, G(<<42,43>>). │ │ │ +{42,43,10795}

    The match operator is allowed everywhere an expression is allowed. It is used │ │ │ to match the value of an expression to a pattern. If multiple match operators │ │ │ -are applied in sequence, they will be evaluated from right to left.

    Examples:

    1> M = #{key => key2, key2 => value}.
    │ │ │ -#{key => key2,key2 => value}
    │ │ │ -2> f(Key), #{Key := Value} = #{key := Key} = M, Value.
    │ │ │ +are applied in sequence, they will be evaluated from right to left.

    Examples:

    1> M = #{key => key2, key2 => value}.
    │ │ │ +#{key => key2,key2 => value}
    │ │ │ +2> f(Key), #{Key := Value} = #{key := Key} = M, Value.
    │ │ │  value
    │ │ │ -3> f(Key), #{Key := Value} = (#{key := Key} = M), Value.
    │ │ │ +3> f(Key), #{Key := Value} = (#{key := Key} = M), Value.
    │ │ │  value
    │ │ │ -4> f(Key), (#{Key := Value} = #{key := Key}) = M, Value.
    │ │ │ +4> f(Key), (#{Key := Value} = #{key := Key}) = M, Value.
    │ │ │  * 1:12: variable 'Key' is unbound
    │ │ │ -5> <<X:Y>> = begin Y = 8, <<42:8>> end, X.
    │ │ │ +5> <<X:Y>> = begin Y = 8, <<42:8>> end, X.
    │ │ │  42

    The expression at prompt 2> first matches the value of variable M against │ │ │ pattern #{key := Key}, binding variable Key. It then matches the value of │ │ │ M against pattern #{Key := Value} using variable Key as the key, binding │ │ │ variable Value.

    The expression at prompt 3> matches expression (#{key := Key} = M) against │ │ │ pattern #{Key := Value}. The expression inside the parentheses is evaluated │ │ │ first. That is, M is matched against #{key := Key}, and then the value of │ │ │ M is matched against pattern #{Key := Value}. That is the same evaluation │ │ │ @@ -276,30 +276,30 @@ │ │ │ binding variable Y and creating a binary. The binary is then matched against │ │ │ pattern <<X:Y>> using the value of Y as the size of the segment.

    │ │ │ │ │ │ │ │ │ │ │ │ Function Calls │ │ │

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

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

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

    In the first form of function calls, ExprM:ExprF(Expr1,...,ExprN), each of │ │ │ ExprM and ExprF must be an atom or an expression that evaluates to an atom. │ │ │ The function is said to be called by using the fully qualified function name. │ │ │ -This is often referred to as a remote or external function call.

    Example:

    lists:keyfind(Name, 1, List)

    In the second form of function calls, ExprF(Expr1,...,ExprN), ExprF must be │ │ │ +This is often referred to as a remote or external function call.

    Example:

    lists:keyfind(Name, 1, List)

    In the second form of function calls, ExprF(Expr1,...,ExprN), ExprF must be │ │ │ an atom or evaluate to a fun.

    If ExprF is an atom, the function is said to be called by using the │ │ │ implicitly qualified function name. If the function ExprF is locally │ │ │ defined, it is called. Alternatively, if ExprF is explicitly imported from the │ │ │ M module, M:ExprF(Expr1,...,ExprN) is called. If ExprF is neither declared │ │ │ locally nor explicitly imported, ExprF must be the name of an automatically │ │ │ -imported BIF.

    Examples:

    handle(Msg, State)
    │ │ │ -spawn(m, init, [])

    Examples where ExprF is a fun:

    1> Fun1 = fun(X) -> X+1 end,
    │ │ │ -Fun1(3).
    │ │ │ +imported BIF.

    Examples:

    handle(Msg, State)
    │ │ │ +spawn(m, init, [])

    Examples where ExprF is a fun:

    1> Fun1 = fun(X) -> X+1 end,
    │ │ │ +Fun1(3).
    │ │ │  4
    │ │ │ -2> fun lists:append/2([1,2], [3,4]).
    │ │ │ -[1,2,3,4]
    │ │ │ +2> fun lists:append/2([1,2], [3,4]).
    │ │ │ +[1,2,3,4]
    │ │ │  3>

    Notice that when calling a local function, there is a difference between using │ │ │ the implicitly or fully qualified function name. The latter always refers to the │ │ │ latest version of the module. See │ │ │ Compilation and Code Loading and │ │ │ Function Evaluation.

    │ │ │ │ │ │ │ │ │ @@ -316,40 +316,40 @@ │ │ │ called instead. This is to avoid that future additions to the set of │ │ │ auto-imported BIFs do not silently change the behavior of old code.

    However, to avoid that old (pre R14) code changed its behavior when compiled │ │ │ with Erlang/OTP version R14A or later, the following restriction applies: If you │ │ │ override the name of a BIF that was auto-imported in OTP versions prior to R14A │ │ │ (ERTS version 5.8) and have an implicitly qualified call to that function in │ │ │ your code, you either need to explicitly remove the auto-import using a compiler │ │ │ directive, or replace the call with a fully qualified function call. Otherwise │ │ │ -you get a compilation error. See the following example:

    -export([length/1,f/1]).
    │ │ │ +you get a compilation error. See the following example:

    -export([length/1,f/1]).
    │ │ │  
    │ │ │ --compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
    │ │ │ +-compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
    │ │ │  
    │ │ │ -length([]) ->
    │ │ │ +length([]) ->
    │ │ │      0;
    │ │ │ -length([H|T]) ->
    │ │ │ -    1 + length(T). %% Calls the local function length/1
    │ │ │ +length([H|T]) ->
    │ │ │ +    1 + length(T). %% Calls the local function length/1
    │ │ │  
    │ │ │ -f(X) when erlang:length(X) > 3 -> %% Calls erlang:length/1,
    │ │ │ +f(X) when erlang:length(X) > 3 -> %% Calls erlang:length/1,
    │ │ │                                    %% which is allowed in guards
    │ │ │      long.

    The same logic applies to explicitly imported functions from other modules, as │ │ │ to locally defined functions. It is not allowed to both import a function from │ │ │ -another module and have the function declared in the module at the same time:

    -export([f/1]).
    │ │ │ +another module and have the function declared in the module at the same time:

    -export([f/1]).
    │ │ │  
    │ │ │ --compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
    │ │ │ +-compile({no_auto_import,[length/1]}). % erlang:length/1 no longer autoimported
    │ │ │  
    │ │ │ --import(mod,[length/1]).
    │ │ │ +-import(mod,[length/1]).
    │ │ │  
    │ │ │ -f(X) when erlang:length(X) > 33 -> %% Calls erlang:length/1,
    │ │ │ +f(X) when erlang:length(X) > 33 -> %% Calls erlang:length/1,
    │ │ │                                     %% which is allowed in guards
    │ │ │  
    │ │ │ -    erlang:length(X);              %% Explicit call to erlang:length in body
    │ │ │ +    erlang:length(X);              %% Explicit call to erlang:length in body
    │ │ │  
    │ │ │ -f(X) ->
    │ │ │ -    length(X).                     %% mod:length/1 is called

    For auto-imported BIFs added in Erlang/OTP R14A and thereafter, overriding the │ │ │ +f(X) -> │ │ │ + length(X). %% mod:length/1 is called

    For auto-imported BIFs added in Erlang/OTP R14A and thereafter, overriding the │ │ │ name with a local function or explicit import is always allowed. However, if the │ │ │ -compile({no_auto_import,[F/A]) directive is not used, the compiler issues a │ │ │ warning whenever the function is called in the module using the implicitly │ │ │ qualified function name.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -361,40 +361,40 @@ │ │ │ ...; │ │ │ GuardSeqN -> │ │ │ BodyN │ │ │ end

    The branches of an if-expression are scanned sequentially until a guard │ │ │ sequence GuardSeq that evaluates to true is found. Then the corresponding │ │ │ Body (a sequence of expressions separated by ,) is evaluated.

    The return value of Body is the return value of the if expression.

    If no guard sequence is evaluated as true, an if_clause run-time error occurs. │ │ │ If necessary, the guard expression true can be used in the last branch, as │ │ │ -that guard sequence is always true.

    Example:

    is_greater_than(X, Y) ->
    │ │ │ +that guard sequence is always true.

    Example:

    is_greater_than(X, Y) ->
    │ │ │      if
    │ │ │          X > Y ->
    │ │ │              true;
    │ │ │          true -> % works as an 'else' branch
    │ │ │              false
    │ │ │      end

    │ │ │ │ │ │ │ │ │ │ │ │ Case │ │ │

    │ │ │
    case Expr of
    │ │ │ -    Pattern1 [when GuardSeq1] ->
    │ │ │ +    Pattern1 [when GuardSeq1] ->
    │ │ │          Body1;
    │ │ │      ...;
    │ │ │ -    PatternN [when GuardSeqN] ->
    │ │ │ +    PatternN [when GuardSeqN] ->
    │ │ │          BodyN
    │ │ │  end

    The expression Expr is evaluated and the patterns Pattern are sequentially │ │ │ matched against the result. If a match succeeds and the optional guard sequence │ │ │ GuardSeq is true, the corresponding Body is evaluated.

    The return value of Body is the return value of the case expression.

    If there is no matching pattern with a true guard sequence, a case_clause │ │ │ -run-time error occurs.

    Example:

    is_valid_signal(Signal) ->
    │ │ │ +run-time error occurs.

    Example:

    is_valid_signal(Signal) ->
    │ │ │      case Signal of
    │ │ │ -        {signal, _What, _From, _To} ->
    │ │ │ +        {signal, _What, _From, _To} ->
    │ │ │              true;
    │ │ │ -        {signal, _What, _To} ->
    │ │ │ +        {signal, _What, _To} ->
    │ │ │              true;
    │ │ │          _Else ->
    │ │ │              false
    │ │ │      end.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -412,57 +412,57 @@ │ │ │ the top-level of a maybe block. It matches the pattern Expr1 against │ │ │ Expr2. If the matching succeeds, any unbound variable in the pattern becomes │ │ │ bound. If the expression is the last expression in the maybe block, it also │ │ │ returns the value of Expr2. If the matching is unsuccessful, the rest of the │ │ │ expressions in the maybe block are skipped and the return value of the maybe │ │ │ block is Expr2.

    None of the variables bound in a maybe block must be used in the code that │ │ │ follows the block.

    Here is an example:

    maybe
    │ │ │ -    {ok, A} ?= a(),
    │ │ │ +    {ok, A} ?= a(),
    │ │ │      true = A >= 0,
    │ │ │ -    {ok, B} ?= b(),
    │ │ │ +    {ok, B} ?= b(),
    │ │ │      A + B
    │ │ │  end

    Let us first assume that a() returns {ok,42} and b() returns {ok,58}. │ │ │ With those return values, all of the match operators will succeed, and the │ │ │ return value of the maybe block is A + B, which is equal to 42 + 58 = 100.

    Now let us assume that a() returns error. The conditional match operator in │ │ │ {ok, A} ?= a() fails to match, and the return value of the maybe block is │ │ │ the value of the expression that failed to match, namely error. Similarly, if │ │ │ b() returns wrong, the return value of the maybe block is wrong.

    Finally, let us assume that a() returns {ok,-1}. Because true = A >= 0 uses │ │ │ the match operator =, a {badmatch,false} run-time error occurs when the │ │ │ -expression fails to match the pattern.

    The example can be written in a less succient way using nested case expressions:

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

    The example can be written in a less succient way using nested case expressions:

    case a() of
    │ │ │ +    {ok, A} ->
    │ │ │          true = A >= 0,
    │ │ │ -        case b() of
    │ │ │ -            {ok, B} ->
    │ │ │ +        case b() of
    │ │ │ +            {ok, B} ->
    │ │ │                  A + B;
    │ │ │              Other1 ->
    │ │ │                  Other1
    │ │ │          end;
    │ │ │      Other2 ->
    │ │ │          Other2
    │ │ │  end

    The maybe block can be augmented with else clauses:

    maybe
    │ │ │      Expr1,
    │ │ │      ...,
    │ │ │      ExprN
    │ │ │  else
    │ │ │ -    Pattern1 [when GuardSeq1] ->
    │ │ │ +    Pattern1 [when GuardSeq1] ->
    │ │ │          Body1;
    │ │ │      ...;
    │ │ │ -    PatternN [when GuardSeqN] ->
    │ │ │ +    PatternN [when GuardSeqN] ->
    │ │ │          BodyN
    │ │ │  end

    If a conditional match operator fails, the failed expression is matched against │ │ │ the patterns in all clauses between the else and end keywords. If a match │ │ │ succeeds and the optional guard sequence GuardSeq is true, the corresponding │ │ │ Body is evaluated. The value returned from the body is the return value of the │ │ │ maybe block.

    If there is no matching pattern with a true guard sequence, an else_clause │ │ │ run-time error occurs.

    None of the variables bound in a maybe block must be used in the else │ │ │ clauses. None of the variables bound in the else clauses must be used in the │ │ │ code that follows the maybe block.

    Here is the previous example augmented with else clauses:

    maybe
    │ │ │ -    {ok, A} ?= a(),
    │ │ │ +    {ok, A} ?= a(),
    │ │ │      true = A >= 0,
    │ │ │ -    {ok, B} ?= b(),
    │ │ │ +    {ok, B} ?= b(),
    │ │ │      A + B
    │ │ │  else
    │ │ │      error -> error;
    │ │ │      wrong -> error
    │ │ │  end

    The else clauses translate the failing value from the conditional match │ │ │ operators to the value error. If the failing value is not one of the │ │ │ recognized values, a else_clause run-time error occurs.

    │ │ │ @@ -481,75 +481,75 @@ │ │ │ {Name,Node} (or a pid located at another node), also never fails.

    │ │ │ │ │ │ │ │ │ │ │ │ Receive │ │ │

    │ │ │
    receive
    │ │ │ -    Pattern1 [when GuardSeq1] ->
    │ │ │ +    Pattern1 [when GuardSeq1] ->
    │ │ │          Body1;
    │ │ │      ...;
    │ │ │ -    PatternN [when GuardSeqN] ->
    │ │ │ +    PatternN [when GuardSeqN] ->
    │ │ │          BodyN
    │ │ │  end

    Fetches a received message present in the message queue of the process. The │ │ │ first message in the message queue is matched sequentially against the patterns │ │ │ from top to bottom. If no match was found, the matching sequence is repeated for │ │ │ the second message in the queue, and so on. Messages are queued in the │ │ │ order they were received. If a match │ │ │ succeeds, that is, if the Pattern matches and the optional guard sequence │ │ │ GuardSeq is true, then the message is removed from the message queue and the │ │ │ corresponding Body is evaluated. All other messages in the message queue │ │ │ remain unchanged.

    The return value of Body is the return value of the receive expression.

    receive never fails. The execution is suspended, possibly indefinitely, until │ │ │ a message arrives that matches one of the patterns and with a true guard │ │ │ -sequence.

    Example:

    wait_for_onhook() ->
    │ │ │ +sequence.

    Example:

    wait_for_onhook() ->
    │ │ │      receive
    │ │ │          onhook ->
    │ │ │ -            disconnect(),
    │ │ │ -            idle();
    │ │ │ -        {connect, B} ->
    │ │ │ -            B ! {busy, self()},
    │ │ │ -            wait_for_onhook()
    │ │ │ +            disconnect(),
    │ │ │ +            idle();
    │ │ │ +        {connect, B} ->
    │ │ │ +            B ! {busy, self()},
    │ │ │ +            wait_for_onhook()
    │ │ │      end.

    The receive expression can be augmented with a timeout:

    receive
    │ │ │ -    Pattern1 [when GuardSeq1] ->
    │ │ │ +    Pattern1 [when GuardSeq1] ->
    │ │ │          Body1;
    │ │ │      ...;
    │ │ │ -    PatternN [when GuardSeqN] ->
    │ │ │ +    PatternN [when GuardSeqN] ->
    │ │ │          BodyN
    │ │ │  after
    │ │ │      ExprT ->
    │ │ │          BodyT
    │ │ │  end

    receive...after works exactly as receive, except that if no matching message │ │ │ has arrived within ExprT milliseconds, then BodyT is evaluated instead. The │ │ │ return value of BodyT then becomes the return value of the receive...after │ │ │ expression. ExprT is to evaluate to an integer, or the atom infinity. The │ │ │ allowed integer range is from 0 to 4294967295, that is, the longest possible │ │ │ timeout is almost 50 days. With a zero value the timeout occurs immediately if │ │ │ there is no matching message in the message queue.

    The atom infinity will make the process wait indefinitely for a matching │ │ │ message. This is the same as not using a timeout. It can be useful for timeout │ │ │ -values that are calculated at runtime.

    Example:

    wait_for_onhook() ->
    │ │ │ +values that are calculated at runtime.

    Example:

    wait_for_onhook() ->
    │ │ │      receive
    │ │ │          onhook ->
    │ │ │ -            disconnect(),
    │ │ │ -            idle();
    │ │ │ -        {connect, B} ->
    │ │ │ -            B ! {busy, self()},
    │ │ │ -            wait_for_onhook()
    │ │ │ +            disconnect(),
    │ │ │ +            idle();
    │ │ │ +        {connect, B} ->
    │ │ │ +            B ! {busy, self()},
    │ │ │ +            wait_for_onhook()
    │ │ │      after
    │ │ │          60000 ->
    │ │ │ -            disconnect(),
    │ │ │ -            error()
    │ │ │ +            disconnect(),
    │ │ │ +            error()
    │ │ │      end.

    It is legal to use a receive...after expression with no branches:

    receive
    │ │ │  after
    │ │ │      ExprT ->
    │ │ │          BodyT
    │ │ │  end

    This construction does not consume any messages, only suspends execution in the │ │ │ -process for ExprT milliseconds. This can be used to implement simple timers.

    Example:

    timer() ->
    │ │ │ -    spawn(m, timer, [self()]).
    │ │ │ +process for ExprT milliseconds. This can be used to implement simple timers.

    Example:

    timer() ->
    │ │ │ +    spawn(m, timer, [self()]).
    │ │ │  
    │ │ │ -timer(Pid) ->
    │ │ │ +timer(Pid) ->
    │ │ │      receive
    │ │ │      after
    │ │ │          5000 ->
    │ │ │              Pid ! timeout
    │ │ │      end.

    For more information on timers in Erlang in general, see the │ │ │ Timers section of the │ │ │ Time and Time Correction in Erlang │ │ │ @@ -591,21 +591,21 @@ │ │ │ false │ │ │ 4> 0.0 =:= -0.0. │ │ │ false │ │ │ 5> 0.0 =:= +0.0. │ │ │ true │ │ │ 6> 1 > a. │ │ │ false │ │ │ -7> #{c => 3} > #{a => 1, b => 2}. │ │ │ +7> #{c => 3} > #{a => 1, b => 2}. │ │ │ false │ │ │ -8> #{a => 1, b => 2} == #{a => 1.0, b => 2.0}. │ │ │ +8> #{a => 1, b => 2} == #{a => 1.0, b => 2.0}. │ │ │ true │ │ │ -9> <<2:2>> < <<128>>. │ │ │ +9> <<2:2>> < <<128>>. │ │ │ true │ │ │ -10> <<3:2>> < <<128>>. │ │ │ +10> <<3:2>> < <<128>>. │ │ │ false

    Note

    Prior to OTP 27, the term equivalence operators considered 0.0 │ │ │ and -0.0 to be the same term.

    This was changed in OTP 27 but legacy code may have expected them to be │ │ │ considered the same. To help users catch errors that may arise from an │ │ │ upgrade, the compiler raises a warning when 0.0 is pattern-matched or used │ │ │ in a term equivalence test.

    If you need to match 0.0 specifically, the warning can be silenced by │ │ │ writing +0.0 instead, which produces the same term but makes the compiler │ │ │ interpret the match as being done on purpose.

    │ │ │ @@ -631,15 +631,15 @@ │ │ │ 0 │ │ │ 8> 2#10 bor 2#01. │ │ │ 3 │ │ │ 9> a + 10. │ │ │ ** exception error: an error occurred when evaluating an arithmetic expression │ │ │ in operator +/2 │ │ │ called as a + 10 │ │ │ -10> 1 bsl (1 bsl 64). │ │ │ +10> 1 bsl (1 bsl 64). │ │ │ ** exception error: a system limit has been reached │ │ │ in operator bsl/2 │ │ │ called as 1 bsl 18446744073709551616

    │ │ │ │ │ │ │ │ │ │ │ │ Boolean Expressions │ │ │ @@ -658,136 +658,136 @@ │ │ │ │ │ │ │ │ │ │ │ │ Short-Circuit Expressions │ │ │

    │ │ │
    Expr1 orelse Expr2
    │ │ │  Expr1 andalso Expr2

    Expr2 is evaluated only if necessary. That is, Expr2 is evaluated only if:

    • Expr1 evaluates to false in an orelse expression.

    or

    • Expr1 evaluates to true in an andalso expression.

    Returns either the value of Expr1 (that is, true or false) or the value of │ │ │ -Expr2 (if Expr2 is evaluated).

    Example 1:

    case A >= -1.0 andalso math:sqrt(A+1) > B of

    This works even if A is less than -1.0, since in that case, math:sqrt/1 is │ │ │ -never evaluated.

    Example 2:

    OnlyOne = is_atom(L) orelse
    │ │ │ -         (is_list(L) andalso length(L) == 1),

    Expr2 is not required to evaluate to a Boolean value. Because of that, │ │ │ -andalso and orelse are tail-recursive.

    Example 3 (tail-recursive function):

    all(Pred, [Hd|Tail]) ->
    │ │ │ -    Pred(Hd) andalso all(Pred, Tail);
    │ │ │ -all(_, []) ->
    │ │ │ +Expr2 (if Expr2 is evaluated).

    Example 1:

    case A >= -1.0 andalso math:sqrt(A+1) > B of

    This works even if A is less than -1.0, since in that case, math:sqrt/1 is │ │ │ +never evaluated.

    Example 2:

    OnlyOne = is_atom(L) orelse
    │ │ │ +         (is_list(L) andalso length(L) == 1),

    Expr2 is not required to evaluate to a Boolean value. Because of that, │ │ │ +andalso and orelse are tail-recursive.

    Example 3 (tail-recursive function):

    all(Pred, [Hd|Tail]) ->
    │ │ │ +    Pred(Hd) andalso all(Pred, Tail);
    │ │ │ +all(_, []) ->
    │ │ │      true.

    Change

    Before Erlang/OTP R13A, Expr2 was required to evaluate to a Boolean value, │ │ │ and as consequence, andalso and orelse were not tail-recursive.

    │ │ │ │ │ │ │ │ │ │ │ │ List Operations │ │ │

    │ │ │
    Expr1 ++ Expr2
    │ │ │  Expr1 -- Expr2

    The list concatenation operator ++ appends its second argument to its first │ │ │ and returns the resulting list.

    The list subtraction operator -- produces a list that is a copy of the first │ │ │ argument. The procedure is as follows: for each element in the second argument, │ │ │ -the first occurrence of this element (if any) is removed.

    Example:

    1> [1,2,3] ++ [4,5].
    │ │ │ -[1,2,3,4,5]
    │ │ │ -2> [1,2,3,2,1,2] -- [2,1,2].
    │ │ │ -[3,1,2]

    │ │ │ +the first occurrence of this element (if any) is removed.

    Example:

    1> [1,2,3] ++ [4,5].
    │ │ │ +[1,2,3,4,5]
    │ │ │ +2> [1,2,3,2,1,2] -- [2,1,2].
    │ │ │ +[3,1,2]

    │ │ │ │ │ │ │ │ │ │ │ │ Map Expressions │ │ │

    │ │ │

    │ │ │ │ │ │ │ │ │ │ │ │ Creating Maps │ │ │

    │ │ │

    Constructing a new map is done by letting an expression K be associated with │ │ │ -another expression V:

    #{K => V}

    New maps can include multiple associations at construction by listing every │ │ │ -association:

    #{K1 => V1, ..., Kn => Vn}

    An empty map is constructed by not associating any terms with each other:

    #{}

    All keys and values in the map are terms. Any expression is first evaluated and │ │ │ +another expression V:

    #{K => V}

    New maps can include multiple associations at construction by listing every │ │ │ +association:

    #{K1 => V1, ..., Kn => Vn}

    An empty map is constructed by not associating any terms with each other:

    #{}

    All keys and values in the map are terms. Any expression is first evaluated and │ │ │ then the resulting terms are used as key and value respectively.

    Keys and values are separated by the => arrow and associations are separated │ │ │ -by a comma (,).

    Examples:

    M0 = #{},                 % empty map
    │ │ │ -M1 = #{a => <<"hello">>}, % single association with literals
    │ │ │ -M2 = #{1 => 2, b => b},   % multiple associations with literals
    │ │ │ -M3 = #{k => {A,B}},       % single association with variables
    │ │ │ -M4 = #{{"w", 1} => f()}.  % compound key associated with an evaluated expression

    Here, A and B are any expressions and M0 through M4 are the resulting │ │ │ -map terms.

    If two matching keys are declared, the latter key takes precedence.

    Example:

    1> #{1 => a, 1 => b}.
    │ │ │ -#{1 => b }
    │ │ │ -2> #{1.0 => a, 1 => b}.
    │ │ │ -#{1 => b, 1.0 => a}

    The order in which the expressions constructing the keys (and their associated │ │ │ +by a comma (,).

    Examples:

    M0 = #{},                 % empty map
    │ │ │ +M1 = #{a => <<"hello">>}, % single association with literals
    │ │ │ +M2 = #{1 => 2, b => b},   % multiple associations with literals
    │ │ │ +M3 = #{k => {A,B}},       % single association with variables
    │ │ │ +M4 = #{{"w", 1} => f()}.  % compound key associated with an evaluated expression

    Here, A and B are any expressions and M0 through M4 are the resulting │ │ │ +map terms.

    If two matching keys are declared, the latter key takes precedence.

    Example:

    1> #{1 => a, 1 => b}.
    │ │ │ +#{1 => b }
    │ │ │ +2> #{1.0 => a, 1 => b}.
    │ │ │ +#{1 => b, 1.0 => a}

    The order in which the expressions constructing the keys (and their associated │ │ │ values) are evaluated is not defined. The syntactic order of the key-value pairs │ │ │ in the construction is of no relevance, except in the recently mentioned case of │ │ │ two matching keys.

    │ │ │ │ │ │ │ │ │ │ │ │ Updating Maps │ │ │

    │ │ │

    Updating a map has a similar syntax as constructing it.

    An expression defining the map to be updated is put in front of the expression │ │ │ -defining the keys to be updated and their respective values:

    M#{K => V}

    Here M is a term of type map and K and V are any expression.

    If key K does not match any existing key in the map, a new association is │ │ │ +defining the keys to be updated and their respective values:

    M#{K => V}

    Here M is a term of type map and K and V are any expression.

    If key K does not match any existing key in the map, a new association is │ │ │ created from key K to value V.

    If key K matches an existing key in map M, its associated value is replaced │ │ │ by the new value V. In both cases, the evaluated map expression returns a new │ │ │ -map.

    If M is not of type map, an exception of type badmap is raised.

    To only update an existing value, the following syntax is used:

    M#{K := V}

    Here M is a term of type map, V is an expression and K is an expression │ │ │ +map.

    If M is not of type map, an exception of type badmap is raised.

    To only update an existing value, the following syntax is used:

    M#{K := V}

    Here M is a term of type map, V is an expression and K is an expression │ │ │ that evaluates to an existing key in M.

    If key K does not match any existing keys in map M, an exception of type │ │ │ badkey is raised at runtime. If a matching key K is present in map M, │ │ │ its associated value is replaced by the new value V, and the evaluated map │ │ │ -expression returns a new map.

    If M is not of type map, an exception of type badmap is raised.

    Examples:

    M0 = #{},
    │ │ │ -M1 = M0#{a => 0},
    │ │ │ -M2 = M1#{a => 1, b => 2},
    │ │ │ -M3 = M2#{"function" => fun() -> f() end},
    │ │ │ -M4 = M3#{a := 2, b := 3}.  % 'a' and 'b' was added in `M1` and `M2`.

    Here M0 is any map. It follows that M1 through M4 are maps as well.

    More examples:

    1> M = #{1 => a}.
    │ │ │ -#{1 => a }
    │ │ │ -2> M#{1.0 => b}.
    │ │ │ -#{1 => a, 1.0 => b}.
    │ │ │ -3> M#{1 := b}.
    │ │ │ -#{1 => b}
    │ │ │ -4> M#{1.0 := b}.
    │ │ │ +expression returns a new map.

    If M is not of type map, an exception of type badmap is raised.

    Examples:

    M0 = #{},
    │ │ │ +M1 = M0#{a => 0},
    │ │ │ +M2 = M1#{a => 1, b => 2},
    │ │ │ +M3 = M2#{"function" => fun() -> f() end},
    │ │ │ +M4 = M3#{a := 2, b := 3}.  % 'a' and 'b' was added in `M1` and `M2`.

    Here M0 is any map. It follows that M1 through M4 are maps as well.

    More examples:

    1> M = #{1 => a}.
    │ │ │ +#{1 => a }
    │ │ │ +2> M#{1.0 => b}.
    │ │ │ +#{1 => a, 1.0 => b}.
    │ │ │ +3> M#{1 := b}.
    │ │ │ +#{1 => b}
    │ │ │ +4> M#{1.0 := b}.
    │ │ │  ** exception error: bad argument

    As in construction, the order in which the key and value expressions are │ │ │ evaluated is not defined. The syntactic order of the key-value pairs in the │ │ │ update is of no relevance, except in the case where two keys match. In that │ │ │ case, the latter value is used.

    │ │ │ │ │ │ │ │ │ │ │ │ Maps in Patterns │ │ │

    │ │ │ -

    Matching of key-value associations from maps is done as follows:

    #{K := V} = M

    Here M is any map. The key K must be a │ │ │ +

    Matching of key-value associations from maps is done as follows:

    #{K := V} = M

    Here M is any map. The key K must be a │ │ │ guard expression, with all variables already │ │ │ bound. V can be any pattern with either bound or unbound variables.

    If the variable V is unbound, it becomes bound to the value associated with │ │ │ the key K, which must exist in the map M. If the variable V is bound, it │ │ │ must match the value associated with K in M.

    Change

    Before Erlang/OTP 23, the expression defining the key K was restricted to be │ │ │ -either a single variable or a literal.

    Example:

    1> M = #{"tuple" => {1,2}}.
    │ │ │ -#{"tuple" => {1,2}}
    │ │ │ -2> #{"tuple" := {1,B}} = M.
    │ │ │ -#{"tuple" => {1,2}}
    │ │ │ +either a single variable or a literal.

    Example:

    1> M = #{"tuple" => {1,2}}.
    │ │ │ +#{"tuple" => {1,2}}
    │ │ │ +2> #{"tuple" := {1,B}} = M.
    │ │ │ +#{"tuple" => {1,2}}
    │ │ │  3> B.
    │ │ │ -2.

    This binds variable B to integer 2.

    Similarly, multiple values from the map can be matched:

    #{K1 := V1, ..., Kn := Vn} = M

    Here keys K1 through Kn are any expressions with literals or bound │ │ │ +2.

    This binds variable B to integer 2.

    Similarly, multiple values from the map can be matched:

    #{K1 := V1, ..., Kn := Vn} = M

    Here keys K1 through Kn are any expressions with literals or bound │ │ │ variables. If all key expressions evaluate successfully and all keys │ │ │ exist in map M, all variables in V1 .. Vn is matched to the │ │ │ associated values of their respective keys.

    If the matching conditions are not met the match fails.

    Note that when matching a map, only the := operator (not the =>) is allowed │ │ │ as a delimiter for the associations.

    The order in which keys are declared in matching has no relevance.

    Duplicate keys are allowed in matching and match each pattern associated to the │ │ │ -keys:

    #{K := V1, K := V2} = M

    The empty map literal (#{}) matches any map when used as a pattern:

    #{} = Expr

    This expression matches if the expression Expr is of type map, otherwise it │ │ │ -fails with an exception badmatch.

    Here the key to be retrieved is constructed from an expression:

    #{{tag,length(List)} := V} = Map

    List must be an already bound variable.

    Matching Syntax

    Matching of literals as keys are allowed in function heads:

    %% only start if not_started
    │ │ │ -handle_call(start, From, #{state := not_started} = S) ->
    │ │ │ +keys:

    #{K := V1, K := V2} = M

    The empty map literal (#{}) matches any map when used as a pattern:

    #{} = Expr

    This expression matches if the expression Expr is of type map, otherwise it │ │ │ +fails with an exception badmatch.

    Here the key to be retrieved is constructed from an expression:

    #{{tag,length(List)} := V} = Map

    List must be an already bound variable.

    Matching Syntax

    Matching of literals as keys are allowed in function heads:

    %% only start if not_started
    │ │ │ +handle_call(start, From, #{state := not_started} = S) ->
    │ │ │  ...
    │ │ │ -    {reply, ok, S#{state := start}};
    │ │ │ +    {reply, ok, S#{state := start}};
    │ │ │  
    │ │ │  %% only change if started
    │ │ │ -handle_call(change, From, #{state := start} = S) ->
    │ │ │ +handle_call(change, From, #{state := start} = S) ->
    │ │ │  ...
    │ │ │ -    {reply, ok, S#{state := changed}};

    │ │ │ + {reply, ok, S#{state := changed}};

    │ │ │ │ │ │ │ │ │ │ │ │ Maps in Guards │ │ │

    │ │ │

    Maps are allowed in guards as long as all subexpressions are valid guard │ │ │ expressions.

    The following guard BIFs handle maps:

    │ │ │ │ │ │ │ │ │ │ │ │ Bit Syntax Expressions │ │ │

    │ │ │

    The bit syntax operates on bit strings. A bit string is a sequence of bits │ │ │ -ordered from the most significant bit to the least significant bit.

    <<>>  % The empty bit string, zero length
    │ │ │ -<<E1>>
    │ │ │ -<<E1,...,En>>

    Each element Ei specifies a segment of the bit string. The segments are │ │ │ +ordered from the most significant bit to the least significant bit.

    <<>>  % The empty bit string, zero length
    │ │ │ +<<E1>>
    │ │ │ +<<E1,...,En>>

    Each element Ei specifies a segment of the bit string. The segments are │ │ │ ordered left to right from the most significant bit to the least significant bit │ │ │ of the bit string.

    Each segment specification Ei is a value, whose default type is integer, │ │ │ followed by an optional size expression and an optional type specifier list.

    Ei = Value |
    │ │ │       Value:Size |
    │ │ │       Value/TypeSpecifierList |
    │ │ │       Value:Size/TypeSpecifierList

    When used in a bit string construction, Value is an expression that is to │ │ │ evaluate to an integer, float, or bit string. If the expression is not a single │ │ │ @@ -798,34 +798,34 @@ │ │ │ guard expression that evaluates to an │ │ │ integer. All variables in the guard expression must be already bound.

    Change

    Before Erlang/OTP 23, Size was restricted to be an integer or a variable │ │ │ bound to an integer.

    The value of Size specifies the size of the segment in units (see below). The │ │ │ default value depends on the type (see below):

    • For integer it is 8.
    • For float it is 64.
    • For binary and bitstring it is the whole binary or bit string.

    In matching, the default value for a binary or bit string segment is only valid │ │ │ for the last element. All other bit string or binary elements in the matching │ │ │ must have a size specification.

    Binaries

    A bit string with a length that is a multiple of 8 bits is known as a binary, │ │ │ which is the most common and useful type of bit string.

    A binary has a canonical representation in memory. Here follows a sequence of │ │ │ -bytes where each byte's value is its sequence number:

    <<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>>

    Bit strings are a later generalization of binaries, so many texts and much │ │ │ -information about binaries apply just as well for bit strings.

    Example:

    1> <<A/binary, B/binary>> = <<"abcde">>.
    │ │ │ +bytes where each byte's value is its sequence number:

    <<1, 2, 3, 4, 5, 6, 7, 8, 9, 10>>

    Bit strings are a later generalization of binaries, so many texts and much │ │ │ +information about binaries apply just as well for bit strings.

    Example:

    1> <<A/binary, B/binary>> = <<"abcde">>.
    │ │ │  * 1:3: a binary field without size is only allowed at the end of a binary pattern
    │ │ │ -2> <<A:3/binary, B/binary>> = <<"abcde">>.
    │ │ │ -<<"abcde">>
    │ │ │ +2> <<A:3/binary, B/binary>> = <<"abcde">>.
    │ │ │ +<<"abcde">>
    │ │ │  3> A.
    │ │ │ -<<"abc">>
    │ │ │ +<<"abc">>
    │ │ │  4> B.
    │ │ │ -<<"de">>

    For the utf8, utf16, and utf32 types, Size must not be given. The size │ │ │ +<<"de">>

    For the utf8, utf16, and utf32 types, Size must not be given. The size │ │ │ of the segment is implicitly determined by the type and value itself.

    TypeSpecifierList is a list of type specifiers, in any order, separated by │ │ │ hyphens (-). Default values are used for any omitted type specifiers.

    • Type= integer | float | binary | bytes | bitstring | bits | │ │ │ utf8 | utf16 | utf32 - The default is integer. bytes is a │ │ │ shorthand for binary and bits is a shorthand for bitstring. See below │ │ │ for more information about the utf types.

    • Signedness= signed | unsigned - Only matters for matching and when │ │ │ the type is integer. The default is unsigned.

    • Endianness= big | little | native - Specifies byte level (octet │ │ │ level) endianness (byte order). Native-endian means that the endianness is │ │ │ resolved at load time to be either big-endian or little-endian, depending on │ │ │ what is native for the CPU that the Erlang machine is run on. Endianness only │ │ │ matters when the Type is either integer, utf16, utf32, or float. The │ │ │ -default is big.

      <<16#1234:16/little>> = <<16#3412:16>> = <<16#34:8, 16#12:8>>
    • Unit= unit:IntegerLiteral - The allowed range is 1 through 256. │ │ │ +default is big.

      <<16#1234:16/little>> = <<16#3412:16>> = <<16#34:8, 16#12:8>>
    • Unit= unit:IntegerLiteral - The allowed range is 1 through 256. │ │ │ Defaults to 1 for integer, float, and bitstring, and to 8 for binary. │ │ │ For types bitstring, bits, and bytes, it is not allowed to specify a │ │ │ unit value different from the default value. No unit specifier must be given │ │ │ for the types utf8, utf16, and utf32.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -850,41 +850,41 @@ │ │ │ │ │ │ Binary segments │ │ │

    │ │ │

    In this section, the phrase "binary segment" refers to any one of the segment │ │ │ types binary, bitstring, bytes, and bits.

    See also the paragraphs about Binaries.

    When constructing binaries and no size is specified for a binary segment, the │ │ │ entire binary value is interpolated into the binary being constructed. However, │ │ │ the size in bits of the binary being interpolated must be evenly divisible by │ │ │ -the unit value for the segment; otherwise an exception is raised.

    For example, the following examples all succeed:

    1> <<(<<"abc">>)/bitstring>>.
    │ │ │ -<<"abc">>
    │ │ │ -2> <<(<<"abc">>)/binary-unit:1>>.
    │ │ │ -<<"abc">>
    │ │ │ -3> <<(<<"abc">>)/binary>>.
    │ │ │ -<<"abc">>

    The first two examples have a unit value of 1 for the segment, while the third │ │ │ +the unit value for the segment; otherwise an exception is raised.

    For example, the following examples all succeed:

    1> <<(<<"abc">>)/bitstring>>.
    │ │ │ +<<"abc">>
    │ │ │ +2> <<(<<"abc">>)/binary-unit:1>>.
    │ │ │ +<<"abc">>
    │ │ │ +3> <<(<<"abc">>)/binary>>.
    │ │ │ +<<"abc">>

    The first two examples have a unit value of 1 for the segment, while the third │ │ │ segment has a unit value of 8.

    Attempting to interpolate a bit string of size 1 into a binary segment with unit │ │ │ -8 (the default unit for binary) fails as shown in this example:

    1> <<(<<1:1>>)/binary>>.
    │ │ │ -** exception error: bad argument

    For the construction to succeed, the unit value of the segment must be 1:

    2> <<(<<1:1>>)/bitstring>>.
    │ │ │ -<<1:1>>
    │ │ │ -3> <<(<<1:1>>)/binary-unit:1>>.
    │ │ │ -<<1:1>>

    Similarly, when matching a binary segment with no size specified, the match │ │ │ +8 (the default unit for binary) fails as shown in this example:

    1> <<(<<1:1>>)/binary>>.
    │ │ │ +** exception error: bad argument

    For the construction to succeed, the unit value of the segment must be 1:

    2> <<(<<1:1>>)/bitstring>>.
    │ │ │ +<<1:1>>
    │ │ │ +3> <<(<<1:1>>)/binary-unit:1>>.
    │ │ │ +<<1:1>>

    Similarly, when matching a binary segment with no size specified, the match │ │ │ succeeds if and only if the size in bits of the rest of the binary is evenly │ │ │ -divisible by the unit value:

    1> <<_/binary-unit:16>> = <<"">>.
    │ │ │ -<<>>
    │ │ │ -2> <<_/binary-unit:16>> = <<"a">>.
    │ │ │ +divisible by the unit value:

    1> <<_/binary-unit:16>> = <<"">>.
    │ │ │ +<<>>
    │ │ │ +2> <<_/binary-unit:16>> = <<"a">>.
    │ │ │  ** exception error: no match of right hand side value <<"a">>
    │ │ │ -3> <<_/binary-unit:16>> = <<"ab">>.
    │ │ │ -<<"ab">>
    │ │ │ -4> <<_/binary-unit:16>> = <<"abc">>.
    │ │ │ +3> <<_/binary-unit:16>> = <<"ab">>.
    │ │ │ +<<"ab">>
    │ │ │ +4> <<_/binary-unit:16>> = <<"abc">>.
    │ │ │  ** exception error: no match of right hand side value <<"abc">>
    │ │ │ -5> <<_/binary-unit:16>> = <<"abcd">>.
    │ │ │ -<<"abcd">>

    When a size is explicitly specified for a binary segment, the segment size in │ │ │ +5> <<_/binary-unit:16>> = <<"abcd">>. │ │ │ +<<"abcd">>

    When a size is explicitly specified for a binary segment, the segment size in │ │ │ bits is the value of Size multiplied by the default or explicit unit value.

    When constructing binaries, the size of the binary being interpolated into the │ │ │ -constructed binary must be at least as large as the size of the binary segment.

    Examples:

    1> <<(<<"abc">>):2/binary>>.
    │ │ │ -<<"ab">>
    │ │ │ -2> <<(<<"a">>):2/binary>>.
    │ │ │ +constructed binary must be at least as large as the size of the binary segment.

    Examples:

    1> <<(<<"abc">>):2/binary>>.
    │ │ │ +<<"ab">>
    │ │ │ +2> <<(<<"a">>):2/binary>>.
    │ │ │  ** exception error: construction of binary failed
    │ │ │          *** segment 1 of type 'binary': the value <<"a">> is shorter than the size of the segment

    │ │ │ │ │ │ │ │ │ │ │ │ Unicode segments │ │ │

    │ │ │ @@ -900,78 +900,78 @@ │ │ │ range 0 through 16#D7FF or 16#E000 through 16#10FFFF. The match fails if the │ │ │ returned value falls outside those ranges.

    A segment of type utf8 matches 1-4 bytes in the bit string, if the bit string │ │ │ at the match position contains a valid UTF-8 sequence. (See RFC-3629 or the │ │ │ Unicode standard.)

    A segment of type utf16 can match 2 or 4 bytes in the bit string. The match │ │ │ fails if the bit string at the match position does not contain a legal UTF-16 │ │ │ encoding of a Unicode code point. (See RFC-2781 or the Unicode standard.)

    A segment of type utf32 can match 4 bytes in the bit string in the same way as │ │ │ an integer segment matches 32 bits. The match fails if the resulting integer │ │ │ -is outside the legal ranges previously mentioned.

    Examples:

    1> Bin1 = <<1,17,42>>.
    │ │ │ -<<1,17,42>>
    │ │ │ -2> Bin2 = <<"abc">>.
    │ │ │ -<<97,98,99>>
    │ │ │ +is outside the legal ranges previously mentioned.

    Examples:

    1> Bin1 = <<1,17,42>>.
    │ │ │ +<<1,17,42>>
    │ │ │ +2> Bin2 = <<"abc">>.
    │ │ │ +<<97,98,99>>
    │ │ │  
    │ │ │ -3> Bin3 = <<1,17,42:16>>.
    │ │ │ -<<1,17,0,42>>
    │ │ │ -4> <<A,B,C:16>> = <<1,17,42:16>>.
    │ │ │ -<<1,17,0,42>>
    │ │ │ +3> Bin3 = <<1,17,42:16>>.
    │ │ │ +<<1,17,0,42>>
    │ │ │ +4> <<A,B,C:16>> = <<1,17,42:16>>.
    │ │ │ +<<1,17,0,42>>
    │ │ │  5> C.
    │ │ │  42
    │ │ │ -6> <<D:16,E,F>> = <<1,17,42:16>>.
    │ │ │ -<<1,17,0,42>>
    │ │ │ +6> <<D:16,E,F>> = <<1,17,42:16>>.
    │ │ │ +<<1,17,0,42>>
    │ │ │  7> D.
    │ │ │  273
    │ │ │  8> F.
    │ │ │  42
    │ │ │ -9> <<G,H/binary>> = <<1,17,42:16>>.
    │ │ │ -<<1,17,0,42>>
    │ │ │ +9> <<G,H/binary>> = <<1,17,42:16>>.
    │ │ │ +<<1,17,0,42>>
    │ │ │  10> H.
    │ │ │ -<<17,0,42>>
    │ │ │ -11> <<G,J/bitstring>> = <<1,17,42:12>>.
    │ │ │ -<<1,17,2,10:4>>
    │ │ │ +<<17,0,42>>
    │ │ │ +11> <<G,J/bitstring>> = <<1,17,42:12>>.
    │ │ │ +<<1,17,2,10:4>>
    │ │ │  12> J.
    │ │ │ -<<17,2,10:4>>
    │ │ │ +<<17,2,10:4>>
    │ │ │  
    │ │ │ -13> <<1024/utf8>>.
    │ │ │ -<<208,128>>
    │ │ │ +13> <<1024/utf8>>.
    │ │ │ +<<208,128>>
    │ │ │  
    │ │ │ -14> <<1:1,0:7>>.
    │ │ │ -<<128>>
    │ │ │ -15> <<16#123:12/little>> = <<16#231:12>> = <<2:4, 3:4, 1:4>>.
    │ │ │ -<<35,1:4>>

    Notice that bit string patterns cannot be nested.

    Notice also that "B=<<1>>" is interpreted as "B =< <1>>" which is a syntax │ │ │ +14> <<1:1,0:7>>. │ │ │ +<<128>> │ │ │ +15> <<16#123:12/little>> = <<16#231:12>> = <<2:4, 3:4, 1:4>>. │ │ │ +<<35,1:4>>

    Notice that bit string patterns cannot be nested.

    Notice also that "B=<<1>>" is interpreted as "B =< <1>>" which is a syntax │ │ │ error. The correct way is to write a space after =: "B = <<1>>.

    More examples are provided in Programming Examples.

    │ │ │ │ │ │ │ │ │ │ │ │ Fun Expressions │ │ │

    │ │ │
    fun
    │ │ │ -    [Name](Pattern11,...,Pattern1N) [when GuardSeq1] ->
    │ │ │ +    [Name](Pattern11,...,Pattern1N) [when GuardSeq1] ->
    │ │ │                Body1;
    │ │ │      ...;
    │ │ │ -    [Name](PatternK1,...,PatternKN) [when GuardSeqK] ->
    │ │ │ +    [Name](PatternK1,...,PatternKN) [when GuardSeqK] ->
    │ │ │                BodyK
    │ │ │  end

    A fun expression begins with the keyword fun and ends with the keyword end. │ │ │ Between them is to be a function declaration, similar to a │ │ │ regular function declaration, │ │ │ except that the function name is optional and is to be a variable, if any.

    Variables in a fun head shadow the function name and both shadow variables in │ │ │ the function clause surrounding the fun expression. Variables bound in a fun │ │ │ -body are local to the fun body.

    The return value of the expression is the resulting fun.

    Examples:

    1> Fun1 = fun (X) -> X+1 end.
    │ │ │ +body are local to the fun body.

    The return value of the expression is the resulting fun.

    Examples:

    1> Fun1 = fun (X) -> X+1 end.
    │ │ │  #Fun<erl_eval.6.39074546>
    │ │ │ -2> Fun1(2).
    │ │ │ +2> Fun1(2).
    │ │ │  3
    │ │ │ -3> Fun2 = fun (X) when X>=5 -> gt; (X) -> lt end.
    │ │ │ +3> Fun2 = fun (X) when X>=5 -> gt; (X) -> lt end.
    │ │ │  #Fun<erl_eval.6.39074546>
    │ │ │ -4> Fun2(7).
    │ │ │ +4> Fun2(7).
    │ │ │  gt
    │ │ │ -5> Fun3 = fun Fact(1) -> 1; Fact(X) when X > 1 -> X * Fact(X - 1) end.
    │ │ │ +5> Fun3 = fun Fact(1) -> 1; Fact(X) when X > 1 -> X * Fact(X - 1) end.
    │ │ │  #Fun<erl_eval.6.39074546>
    │ │ │ -6> Fun3(4).
    │ │ │ +6> Fun3(4).
    │ │ │  24

    The following fun expressions are also allowed:

    fun Name/Arity
    │ │ │  fun Module:Name/Arity

    In Name/Arity, Name is an atom and Arity is an integer. Name/Arity must │ │ │ -specify an existing local function. The expression is syntactic sugar for:

    fun (Arg1,...,ArgN) -> Name(Arg1,...,ArgN) end

    In Module:Name/Arity, Module, and Name are atoms and Arity is an │ │ │ +specify an existing local function. The expression is syntactic sugar for:

    fun (Arg1,...,ArgN) -> Name(Arg1,...,ArgN) end

    In Module:Name/Arity, Module, and Name are atoms and Arity is an │ │ │ integer. Module, Name, and Arity can also be variables. A fun defined in │ │ │ this way refers to the function Name with arity Arity in the latest │ │ │ version of module Module. A fun defined in this way is not dependent on the │ │ │ code for the module in which it is defined.

    Change

    Before Erlang/OTP R15, Module, Name, and Arity were not allowed to be │ │ │ variables.

    More examples are provided in Programming Examples.

    │ │ │ │ │ │ │ │ │ @@ -981,35 +981,35 @@ │ │ │
    catch Expr

    Returns the value of Expr unless an exception is raised during the evaluation. In │ │ │ that case, the exception is caught. The return value depends on the class of the │ │ │ exception:

    Reason depends on the type of error that occurred, and Stack is the stack of │ │ │ recent function calls, see Exit Reasons.

    Examples:

    1> catch 1+2.
    │ │ │  3
    │ │ │  2> catch 1+a.
    │ │ │ -{'EXIT',{badarith,[...]}}

    The BIF throw(Any) can be used for non-local return from a │ │ │ -function. It must be evaluated within a catch, which returns the value Any.

    Example:

    3> catch throw(hello).
    │ │ │ +{'EXIT',{badarith,[...]}}

    The BIF throw(Any) can be used for non-local return from a │ │ │ +function. It must be evaluated within a catch, which returns the value Any.

    Example:

    3> catch throw(hello).
    │ │ │  hello

    If throw/1 is not evaluated within a catch, a nocatch run-time │ │ │ error occurs.

    Change

    Before Erlang/OTP 24, the catch operator had the lowest precedence, making │ │ │ -it necessary to add parentheses when combining it with the match operator:

    1> A = (catch 42).
    │ │ │ +it necessary to add parentheses when combining it with the match operator:

    1> A = (catch 42).
    │ │ │  42
    │ │ │  2> A.
    │ │ │  42

    Starting from Erlang/OTP 24, the parentheses can be omitted:

    1> A = catch 42.
    │ │ │  42
    │ │ │  2> A.
    │ │ │  42

    │ │ │ │ │ │ │ │ │ │ │ │ Try │ │ │

    │ │ │
    try Exprs
    │ │ │  catch
    │ │ │ -    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
    │ │ │ +    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
    │ │ │          ExceptionBody1;
    │ │ │ -    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
    │ │ │ +    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
    │ │ │          ExceptionBodyN
    │ │ │  end

    This is an enhancement of catch. It gives the │ │ │ possibility to:

    • Distinguish between different exception classes.
    • Choose to handle only the desired ones.
    • Passing the others on to an enclosing try or catch, or to default error │ │ │ handling.

    Notice that although the keyword catch is used in the try expression, there │ │ │ is not a catch expression within the try expression.

    It returns the value of Exprs (a sequence of expressions Expr1, ..., ExprN) │ │ │ unless an exception occurs during the evaluation. In that case the exception is │ │ │ caught and the patterns ExceptionPattern with the right exception class │ │ │ @@ -1019,47 +1019,47 @@ │ │ │ stack trace is bound to the variable when the corresponding ExceptionPattern │ │ │ matches.

    If an exception occurs during evaluation of Exprs but there is no matching │ │ │ ExceptionPattern of the right Class with a true guard sequence, the │ │ │ exception is passed on as if Exprs had not been enclosed in a try │ │ │ expression.

    If an exception occurs during evaluation of ExceptionBody, it is not caught.

    It is allowed to omit Class and Stacktrace. An omitted Class is shorthand │ │ │ for throw:

    try Exprs
    │ │ │  catch
    │ │ │ -    ExceptionPattern1 [when ExceptionGuardSeq1] ->
    │ │ │ +    ExceptionPattern1 [when ExceptionGuardSeq1] ->
    │ │ │          ExceptionBody1;
    │ │ │ -    ExceptionPatternN [when ExceptionGuardSeqN] ->
    │ │ │ +    ExceptionPatternN [when ExceptionGuardSeqN] ->
    │ │ │          ExceptionBodyN
    │ │ │  end

    The try expression can have an of section:

    try Exprs of
    │ │ │ -    Pattern1 [when GuardSeq1] ->
    │ │ │ +    Pattern1 [when GuardSeq1] ->
    │ │ │          Body1;
    │ │ │      ...;
    │ │ │ -    PatternN [when GuardSeqN] ->
    │ │ │ +    PatternN [when GuardSeqN] ->
    │ │ │          BodyN
    │ │ │  catch
    │ │ │ -    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
    │ │ │ +    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
    │ │ │          ExceptionBody1;
    │ │ │      ...;
    │ │ │ -    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
    │ │ │ +    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
    │ │ │          ExceptionBodyN
    │ │ │  end

    If the evaluation of Exprs succeeds without an exception, the patterns │ │ │ Pattern are sequentially matched against the result in the same way as for a │ │ │ case expression, except that if the matching fails, a │ │ │ try_clause run-time error occurs instead of a case_clause.

    Only exceptions occurring during the evaluation of Exprs can be caught by the │ │ │ catch section. Exceptions occurring in a Body or due to a failed match are │ │ │ not caught.

    The try expression can also be augmented with an after section, intended to │ │ │ be used for cleanup with side effects:

    try Exprs of
    │ │ │ -    Pattern1 [when GuardSeq1] ->
    │ │ │ +    Pattern1 [when GuardSeq1] ->
    │ │ │          Body1;
    │ │ │      ...;
    │ │ │ -    PatternN [when GuardSeqN] ->
    │ │ │ +    PatternN [when GuardSeqN] ->
    │ │ │          BodyN
    │ │ │  catch
    │ │ │ -    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
    │ │ │ +    Class1:ExceptionPattern1[:Stacktrace] [when ExceptionGuardSeq1] ->
    │ │ │          ExceptionBody1;
    │ │ │      ...;
    │ │ │ -    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
    │ │ │ +    ClassN:ExceptionPatternN[:Stacktrace] [when ExceptionGuardSeqN] ->
    │ │ │          ExceptionBodyN
    │ │ │  after
    │ │ │      AfterBody
    │ │ │  end

    AfterBody is evaluated after either Body or ExceptionBody, no matter which │ │ │ one. The evaluated value of AfterBody is lost; the return value of the try │ │ │ expression is the same with an after section as without.

    Even if an exception occurs during evaluation of Body or ExceptionBody, │ │ │ AfterBody is evaluated. In this case the exception is passed on after │ │ │ @@ -1082,40 +1082,40 @@ │ │ │ ExpressionBody │ │ │ after │ │ │ AfterBody │ │ │ end │ │ │ │ │ │ try Exprs after AfterBody end

    Next is an example of using after. This closes the file, even in the event of │ │ │ exceptions in file:read/2 or in binary_to_term/1. The │ │ │ -exceptions are the same as without the try...after...end expression:

    termize_file(Name) ->
    │ │ │ -    {ok,F} = file:open(Name, [read,binary]),
    │ │ │ +exceptions are the same as without the try...after...end expression:

    termize_file(Name) ->
    │ │ │ +    {ok,F} = file:open(Name, [read,binary]),
    │ │ │      try
    │ │ │ -        {ok,Bin} = file:read(F, 1024*1024),
    │ │ │ -        binary_to_term(Bin)
    │ │ │ +        {ok,Bin} = file:read(F, 1024*1024),
    │ │ │ +        binary_to_term(Bin)
    │ │ │      after
    │ │ │ -        file:close(F)
    │ │ │ +        file:close(F)
    │ │ │      end.

    Next is an example of using try to emulate catch Expr:

    try Expr
    │ │ │  catch
    │ │ │      throw:Term -> Term;
    │ │ │ -    exit:Reason -> {'EXIT',Reason};
    │ │ │ -    error:Reason:Stk -> {'EXIT',{Reason,Stk}}
    │ │ │ +    exit:Reason -> {'EXIT',Reason};
    │ │ │ +    error:Reason:Stk -> {'EXIT',{Reason,Stk}}
    │ │ │  end

    Variables bound in the various parts of these expressions have different scopes. │ │ │ Variables bound just after the try keyword are:

    • bound in the of section
    • unsafe in both the catch and after sections, as well as after the whole │ │ │ construct

    Variables bound in of section are:

    • unbound in the catch section
    • unsafe in both the after section, as well as after the whole construct

    Variables bound in the catch section are unsafe in the after section, as │ │ │ well as after the whole construct.

    Variables bound in the after section are unsafe after the whole construct.

    │ │ │ │ │ │ │ │ │ │ │ │ Parenthesized Expressions │ │ │

    │ │ │ -
    (Expr)

    Parenthesized expressions are useful to override │ │ │ +

    (Expr)

    Parenthesized expressions are useful to override │ │ │ operator precedences, for example, in arithmetic │ │ │ expressions:

    1> 1 + 2 * 3.
    │ │ │  7
    │ │ │ -2> (1 + 2) * 3.
    │ │ │ +2> (1 + 2) * 3.
    │ │ │  9

    │ │ │ │ │ │ │ │ │ │ │ │ Block Expressions │ │ │

    │ │ │
    begin
    │ │ │ @@ -1127,71 +1127,71 @@
    │ │ │    
    │ │ │      
    │ │ │    
    │ │ │    Comprehensions
    │ │ │  

    │ │ │

    Comprehensions provide a succinct notation for iterating over one or more terms │ │ │ and constructing a new term. Comprehensions come in three different flavors, │ │ │ -depending on the type of term they build.

    List comprehensions construct lists. They have the following syntax:

    [Expr || Qualifier1, . . ., QualifierN]

    Here, Expr is an arbitrary expression, and each Qualifier is either a │ │ │ +depending on the type of term they build.

    List comprehensions construct lists. They have the following syntax:

    [Expr || Qualifier1, . . ., QualifierN]

    Here, Expr is an arbitrary expression, and each Qualifier is either a │ │ │ generator or a filter.

    Bit string comprehensions construct bit strings or binaries. They have the │ │ │ -following syntax:

    << BitStringExpr || Qualifier1, . . ., QualifierN >>

    BitStringExpr is an expression that evaluates to a bit string. If │ │ │ +following syntax:

    << BitStringExpr || Qualifier1, . . ., QualifierN >>

    BitStringExpr is an expression that evaluates to a bit string. If │ │ │ BitStringExpr is a function call, it must be enclosed in parentheses. Each │ │ │ -Qualifier is either a generator or a filter.

    Map comprehensions construct maps. They have the following syntax:

    #{KeyExpr => ValueExpr || Qualifier1, . . ., QualifierN}

    Here, KeyExpr and ValueExpr are arbitrary expressions, and each Qualifier │ │ │ +Qualifier is either a generator or a filter.

    Map comprehensions construct maps. They have the following syntax:

    #{KeyExpr => ValueExpr || Qualifier1, . . ., QualifierN}

    Here, KeyExpr and ValueExpr are arbitrary expressions, and each Qualifier │ │ │ is either a generator or a filter.

    Change

    Map comprehensions and map generators were introduced in Erlang/OTP 26.

    There are three kinds of generators.

    A list generator has the following syntax:

    Pattern <- ListExpr

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

    A bit string generator has the following syntax:

    BitstringPattern <= BitStringExpr

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

    A map generator has the following syntax:

    KeyPattern := ValuePattern <- MapExpression

    where MapExpr is an expression that evaluates to a map, or a map iterator │ │ │ obtained by calling maps:iterator/1 or maps:iterator/2.

    A filter is an expression that evaluates to true or false.

    The variables in the generator patterns shadow previously bound variables, │ │ │ including variables bound in a previous generator pattern.

    Variables bound in a generator expression are not visible outside the │ │ │ -expression:

    1> [{E,L} || E <- L=[1,2,3]].
    │ │ │ +expression:

    1> [{E,L} || E <- L=[1,2,3]].
    │ │ │  * 1:5: variable 'L' is unbound

    A list comprehension returns a list, where the list elements are the result │ │ │ of evaluating Expr for each combination of generator elements for which all │ │ │ filters are true.

    A bit string comprehension returns a bit string, which is created by │ │ │ concatenating the results of evaluating BitStringExpr for each combination of │ │ │ bit string generator elements for which all filters are true.

    A map comprehension returns a map, where the map elements are the result of │ │ │ evaluating KeyExpr and ValueExpr for each combination of generator elements │ │ │ for which all filters are true. If the key expressions are not unique, the last │ │ │ -occurrence is stored in the map.

    Examples:

    Multiplying each element in a list by two:

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

    Multiplying each byte in a binary by two, returning a list:

    1> [X*2 || <<X>> <= <<1,2,3>>].
    │ │ │ -[2,4,6]

    Multiplying each byte in a binary by two:

    1> << <<(X*2)>> || <<X>> <= <<1,2,3>> >>.
    │ │ │ -<<2,4,6>>

    Multiplying each element in a list by two, returning a binary:

    1> << <<(X*2)>> || X <- [1,2,3] >>.
    │ │ │ -<<2,4,6>>

    Creating a mapping from an integer to its square:

    1> #{X => X*X || X <- [1,2,3]}.
    │ │ │ -#{1 => 1,2 => 4,3 => 9}

    Multiplying the value of each element in a map by two:

    1> #{K => 2*V || K := V <- #{a => 1,b => 2,c => 3}}.
    │ │ │ -#{a => 2,b => 4,c => 6}

    Filtering a list, keeping odd numbers:

    1> [X || X <- [1,2,3,4,5], X rem 2 =:= 1].
    │ │ │ -[1,3,5]

    Filtering a list, keeping only elements that match:

    1> [X || {_,_}=X <- [{a,b}, [a], {x,y,z}, {1,2}]].
    │ │ │ -[{a,b},{1,2}]

    Combining elements from two list generators:

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

    More examples are provided in │ │ │ +occurrence is stored in the map.

    Examples:

    Multiplying each element in a list by two:

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

    Multiplying each byte in a binary by two, returning a list:

    1> [X*2 || <<X>> <= <<1,2,3>>].
    │ │ │ +[2,4,6]

    Multiplying each byte in a binary by two:

    1> << <<(X*2)>> || <<X>> <= <<1,2,3>> >>.
    │ │ │ +<<2,4,6>>

    Multiplying each element in a list by two, returning a binary:

    1> << <<(X*2)>> || X <- [1,2,3] >>.
    │ │ │ +<<2,4,6>>

    Creating a mapping from an integer to its square:

    1> #{X => X*X || X <- [1,2,3]}.
    │ │ │ +#{1 => 1,2 => 4,3 => 9}

    Multiplying the value of each element in a map by two:

    1> #{K => 2*V || K := V <- #{a => 1,b => 2,c => 3}}.
    │ │ │ +#{a => 2,b => 4,c => 6}

    Filtering a list, keeping odd numbers:

    1> [X || X <- [1,2,3,4,5], X rem 2 =:= 1].
    │ │ │ +[1,3,5]

    Filtering a list, keeping only elements that match:

    1> [X || {_,_}=X <- [{a,b}, [a], {x,y,z}, {1,2}]].
    │ │ │ +[{a,b},{1,2}]

    Combining elements from two list generators:

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

    More examples are provided in │ │ │ Programming Examples.

    When there are no generators, a comprehension returns either a term constructed │ │ │ from a single element (the result of evaluating Expr) if all filters are true, │ │ │ or a term constructed from no elements (that is, [] for list comprehension, │ │ │ -<<>> for a bit string comprehension, and #{} for a map comprehension).

    Example:

    1> [2 || is_integer(2)].
    │ │ │ -[2]
    │ │ │ -2> [x || is_integer(x)].
    │ │ │ -[]

    What happens when the filter expression does not evaluate to a boolean value │ │ │ +<<>> for a bit string comprehension, and #{} for a map comprehension).

    Example:

    1> [2 || is_integer(2)].
    │ │ │ +[2]
    │ │ │ +2> [x || is_integer(x)].
    │ │ │ +[]

    What happens when the filter expression does not evaluate to a boolean value │ │ │ depends on the expression:

    • If the expression is a guard expression, │ │ │ failure to evaluate or evaluating to a non-boolean value is equivalent to │ │ │ evaluating to false.
    • If the expression is not a guard expression and evaluates to a non-Boolean │ │ │ value Val, an exception {bad_filter, Val} is triggered at runtime. If the │ │ │ evaluation of the expression raises an exception, it is not caught by the │ │ │ -comprehension.

    Examples (using a guard expression as filter):

    1> List = [1,2,a,b,c,3,4].
    │ │ │ -[1,2,a,b,c,3,4]
    │ │ │ -2> [E || E <- List, E rem 2].
    │ │ │ -[]
    │ │ │ -3> [E || E <- List, E rem 2 =:= 0].
    │ │ │ -[2,4]

    Examples (using a non-guard expression as filter):

    1> List = [1,2,a,b,c,3,4].
    │ │ │ -[1,2,a,b,c,3,4]
    │ │ │ -2> FaultyIsEven = fun(E) -> E rem 2 end.
    │ │ │ +comprehension.

    Examples (using a guard expression as filter):

    1> List = [1,2,a,b,c,3,4].
    │ │ │ +[1,2,a,b,c,3,4]
    │ │ │ +2> [E || E <- List, E rem 2].
    │ │ │ +[]
    │ │ │ +3> [E || E <- List, E rem 2 =:= 0].
    │ │ │ +[2,4]

    Examples (using a non-guard expression as filter):

    1> List = [1,2,a,b,c,3,4].
    │ │ │ +[1,2,a,b,c,3,4]
    │ │ │ +2> FaultyIsEven = fun(E) -> E rem 2 end.
    │ │ │  #Fun<erl_eval.42.17316486>
    │ │ │ -3> [E || E <- List, FaultyIsEven(E)].
    │ │ │ +3> [E || E <- List, FaultyIsEven(E)].
    │ │ │  ** exception error: bad filter 1
    │ │ │ -4> IsEven = fun(E) -> E rem 2 =:= 0 end.
    │ │ │ +4> IsEven = fun(E) -> E rem 2 =:= 0 end.
    │ │ │  #Fun<erl_eval.42.17316486>
    │ │ │ -5> [E || E <- List, IsEven(E)].
    │ │ │ +5> [E || E <- List, IsEven(E)].
    │ │ │  ** exception error: an error occurred when evaluating an arithmetic expression
    │ │ │       in operator  rem/2
    │ │ │          called as a rem 2
    │ │ │ -6> [E || E <- List, is_integer(E), IsEven(E)].
    │ │ │ -[2,4]

    │ │ │ +6> [E || E <- List, is_integer(E), IsEven(E)]. │ │ │ +[2,4]

    │ │ │ │ │ │ │ │ │ │ │ │ Guard Sequences │ │ │

    │ │ │

    A guard sequence is a sequence of guards, separated by semicolon (;). The │ │ │ guard sequence is true if at least one of the guards is true. (The remaining │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/funs.html │ │ │ @@ -117,402 +117,402 @@ │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ │ │ │ map │ │ │

    │ │ │ -

    The following function, double, doubles every element in a list:

    double([H|T]) -> [2*H|double(T)];
    │ │ │ -double([])    -> [].

    Hence, the argument entered as input is doubled as follows:

    > double([1,2,3,4]).
    │ │ │ -[2,4,6,8]

    The following function, add_one, adds one to every element in a list:

    add_one([H|T]) -> [H+1|add_one(T)];
    │ │ │ -add_one([])    -> [].

    The functions double and add_one have a similar structure. This can be used │ │ │ -by writing a function map that expresses this similarity:

    map(F, [H|T]) -> [F(H)|map(F, T)];
    │ │ │ -map(F, [])    -> [].

    The functions double and add_one can now be expressed in terms of map as │ │ │ -follows:

    double(L)  -> map(fun(X) -> 2*X end, L).
    │ │ │ -add_one(L) -> map(fun(X) -> 1 + X end, L).

    map(F, List) is a function that takes a function F and a list L as │ │ │ +

    The following function, double, doubles every element in a list:

    double([H|T]) -> [2*H|double(T)];
    │ │ │ +double([])    -> [].

    Hence, the argument entered as input is doubled as follows:

    > double([1,2,3,4]).
    │ │ │ +[2,4,6,8]

    The following function, add_one, adds one to every element in a list:

    add_one([H|T]) -> [H+1|add_one(T)];
    │ │ │ +add_one([])    -> [].

    The functions double and add_one have a similar structure. This can be used │ │ │ +by writing a function map that expresses this similarity:

    map(F, [H|T]) -> [F(H)|map(F, T)];
    │ │ │ +map(F, [])    -> [].

    The functions double and add_one can now be expressed in terms of map as │ │ │ +follows:

    double(L)  -> map(fun(X) -> 2*X end, L).
    │ │ │ +add_one(L) -> map(fun(X) -> 1 + X end, L).

    map(F, List) is a function that takes a function F and a list L as │ │ │ arguments and returns a new list, obtained by applying F to each of the │ │ │ elements in L.

    The process of abstracting out the common features of a number of different │ │ │ programs is called procedural abstraction. Procedural abstraction can be used │ │ │ to write several different functions that have a similar structure, but differ │ │ │ in some minor detail. This is done as follows:

    1. Step 1. Write one function that represents the common features of these │ │ │ functions.
    2. Step 2. Parameterize the difference in terms of functions that are passed │ │ │ as arguments to the common function.

    │ │ │ │ │ │ │ │ │ │ │ │ foreach │ │ │

    │ │ │

    This section illustrates procedural abstraction. Initially, the following two │ │ │ -examples are written as conventional functions.

    This function prints all elements of a list onto a stream:

    print_list(Stream, [H|T]) ->
    │ │ │ -    io:format(Stream, "~p~n", [H]),
    │ │ │ -    print_list(Stream, T);
    │ │ │ -print_list(Stream, []) ->
    │ │ │ -    true.

    This function broadcasts a message to a list of processes:

    broadcast(Msg, [Pid|Pids]) ->
    │ │ │ +examples are written as conventional functions.

    This function prints all elements of a list onto a stream:

    print_list(Stream, [H|T]) ->
    │ │ │ +    io:format(Stream, "~p~n", [H]),
    │ │ │ +    print_list(Stream, T);
    │ │ │ +print_list(Stream, []) ->
    │ │ │ +    true.

    This function broadcasts a message to a list of processes:

    broadcast(Msg, [Pid|Pids]) ->
    │ │ │      Pid ! Msg,
    │ │ │ -    broadcast(Msg, Pids);
    │ │ │ -broadcast(_, []) ->
    │ │ │ +    broadcast(Msg, Pids);
    │ │ │ +broadcast(_, []) ->
    │ │ │      true.

    These two functions have a similar structure. They both iterate over a list and │ │ │ do something to each element in the list. The "something" is passed on as an │ │ │ -extra argument to the function that does this.

    The function foreach expresses this similarity:

    foreach(F, [H|T]) ->
    │ │ │ -    F(H),
    │ │ │ -    foreach(F, T);
    │ │ │ -foreach(F, []) ->
    │ │ │ -    ok.

    Using the function foreach, the function print_list becomes:

    foreach(fun(H) -> io:format(S, "~p~n",[H]) end, L)

    Using the function foreach, the function broadcast becomes:

    foreach(fun(Pid) -> Pid ! M end, L)

    foreach is evaluated for its side-effect and not its value. foreach(Fun ,L) │ │ │ +extra argument to the function that does this.

    The function foreach expresses this similarity:

    foreach(F, [H|T]) ->
    │ │ │ +    F(H),
    │ │ │ +    foreach(F, T);
    │ │ │ +foreach(F, []) ->
    │ │ │ +    ok.

    Using the function foreach, the function print_list becomes:

    foreach(fun(H) -> io:format(S, "~p~n",[H]) end, L)

    Using the function foreach, the function broadcast becomes:

    foreach(fun(Pid) -> Pid ! M end, L)

    foreach is evaluated for its side-effect and not its value. foreach(Fun ,L) │ │ │ calls Fun(X) for each element X in L and the processing occurs in the │ │ │ order that the elements were defined in L. map does not define the order in │ │ │ which its elements are processed.

    │ │ │ │ │ │ │ │ │ │ │ │ Syntax of Funs │ │ │

    │ │ │

    Funs are written with the following syntax (see │ │ │ -Fun Expressions for full description):

    F = fun (Arg1, Arg2, ... ArgN) ->
    │ │ │ +Fun Expressions for full description):

    F = fun (Arg1, Arg2, ... ArgN) ->
    │ │ │          ...
    │ │ │      end

    This creates an anonymous function of N arguments and binds it to the variable │ │ │ F.

    Another function, FunctionName, written in the same module, can be passed as │ │ │ an argument, using the following syntax:

    F = fun FunctionName/Arity

    With this form of function reference, the function that is referred to does not │ │ │ need to be exported from the module.

    It is also possible to refer to a function defined in a different module, with │ │ │ -the following syntax:

    F = fun Module:FunctionName/Arity

    In this case, the function must be exported from the module in question.

    The following program illustrates the different ways of creating funs:

    -module(fun_test).
    │ │ │ --export([t1/0, t2/0]).
    │ │ │ --import(lists, [map/2]).
    │ │ │ +the following syntax:

    F = fun Module:FunctionName/Arity

    In this case, the function must be exported from the module in question.

    The following program illustrates the different ways of creating funs:

    -module(fun_test).
    │ │ │ +-export([t1/0, t2/0]).
    │ │ │ +-import(lists, [map/2]).
    │ │ │  
    │ │ │ -t1() -> map(fun(X) -> 2 * X end, [1,2,3,4,5]).
    │ │ │ +t1() -> map(fun(X) -> 2 * X end, [1,2,3,4,5]).
    │ │ │  
    │ │ │ -t2() -> map(fun double/1, [1,2,3,4,5]).
    │ │ │ +t2() -> map(fun double/1, [1,2,3,4,5]).
    │ │ │  
    │ │ │ -double(X) -> X * 2.

    The fun F can be evaluated with the following syntax:

    F(Arg1, Arg2, ..., Argn)

    To check whether a term is a fun, use the test │ │ │ -is_function/1 in a guard.

    Example:

    f(F, Args) when is_function(F) ->
    │ │ │ -   apply(F, Args);
    │ │ │ -f(N, _) when is_integer(N) ->
    │ │ │ +double(X) -> X * 2.

    The fun F can be evaluated with the following syntax:

    F(Arg1, Arg2, ..., Argn)

    To check whether a term is a fun, use the test │ │ │ +is_function/1 in a guard.

    Example:

    f(F, Args) when is_function(F) ->
    │ │ │ +   apply(F, Args);
    │ │ │ +f(N, _) when is_integer(N) ->
    │ │ │     N.

    Funs are a distinct type. The BIFs erlang:fun_info/1,2 can be used to retrieve │ │ │ information about a fun, and the BIF erlang:fun_to_list/1 returns a textual │ │ │ representation of a fun. The check_process_code/2 │ │ │ BIF returns true if the process contains funs that depend on the old version │ │ │ of a module.

    │ │ │ │ │ │ │ │ │ │ │ │ Variable Bindings Within a Fun │ │ │

    │ │ │

    The scope rules for variables that occur in funs are as follows:

    • All variables that occur in the head of a fun are assumed to be "fresh" │ │ │ variables.
    • Variables that are defined before the fun, and that occur in function calls or │ │ │ -guard tests within the fun, have the values they had outside the fun.
    • Variables cannot be exported from a fun.

    The following examples illustrate these rules:

    print_list(File, List) ->
    │ │ │ -    {ok, Stream} = file:open(File, write),
    │ │ │ -    foreach(fun(X) -> io:format(Stream,"~p~n",[X]) end, List),
    │ │ │ -    file:close(Stream).

    Here, the variable X, defined in the head of the fun, is a new variable. The │ │ │ +guard tests within the fun, have the values they had outside the fun.

  • Variables cannot be exported from a fun.
  • The following examples illustrate these rules:

    print_list(File, List) ->
    │ │ │ +    {ok, Stream} = file:open(File, write),
    │ │ │ +    foreach(fun(X) -> io:format(Stream,"~p~n",[X]) end, List),
    │ │ │ +    file:close(Stream).

    Here, the variable X, defined in the head of the fun, is a new variable. The │ │ │ variable Stream, which is used within the fun, gets its value from the │ │ │ file:open line.

    As any variable that occurs in the head of a fun is considered a new variable, │ │ │ -it is equally valid to write as follows:

    print_list(File, List) ->
    │ │ │ -    {ok, Stream} = file:open(File, write),
    │ │ │ -    foreach(fun(File) ->
    │ │ │ -                io:format(Stream,"~p~n",[File])
    │ │ │ -            end, List),
    │ │ │ -    file:close(Stream).

    Here, File is used as the new variable instead of X. This is not so wise │ │ │ +it is equally valid to write as follows:

    print_list(File, List) ->
    │ │ │ +    {ok, Stream} = file:open(File, write),
    │ │ │ +    foreach(fun(File) ->
    │ │ │ +                io:format(Stream,"~p~n",[File])
    │ │ │ +            end, List),
    │ │ │ +    file:close(Stream).

    Here, File is used as the new variable instead of X. This is not so wise │ │ │ because code in the fun body cannot refer to the variable File, which is │ │ │ defined outside of the fun. Compiling this example gives the following │ │ │ diagnostic:

    ./FileName.erl:Line: Warning: variable 'File'
    │ │ │        shadowed in 'fun'

    This indicates that the variable File, which is defined inside the fun, │ │ │ collides with the variable File, which is defined outside the fun.

    The rules for importing variables into a fun has the consequence that certain │ │ │ pattern matching operations must be moved into guard expressions and cannot be │ │ │ written in the head of the fun. For example, you might write the following code │ │ │ if you intend the first clause of F to be evaluated when the value of its │ │ │ -argument is Y:

    f(...) ->
    │ │ │ +argument is Y:

    f(...) ->
    │ │ │      Y = ...
    │ │ │ -    map(fun(X) when X == Y ->
    │ │ │ +    map(fun(X) when X == Y ->
    │ │ │               ;
    │ │ │ -           (_) ->
    │ │ │ +           (_) ->
    │ │ │               ...
    │ │ │ -        end, ...)
    │ │ │ -    ...

    instead of writing the following code:

    f(...) ->
    │ │ │ +        end, ...)
    │ │ │ +    ...

    instead of writing the following code:

    f(...) ->
    │ │ │      Y = ...
    │ │ │ -    map(fun(Y) ->
    │ │ │ +    map(fun(Y) ->
    │ │ │               ;
    │ │ │ -           (_) ->
    │ │ │ +           (_) ->
    │ │ │               ...
    │ │ │ -        end, ...)
    │ │ │ +        end, ...)
    │ │ │      ...

    │ │ │ │ │ │ │ │ │ │ │ │ Funs and Module Lists │ │ │

    │ │ │

    The following examples show a dialogue with the Erlang shell. All the higher │ │ │ order functions discussed are exported from the module lists.

    │ │ │ │ │ │ │ │ │ │ │ │ map │ │ │

    │ │ │ -

    lists:map/2 takes a function of one argument and a list of terms:

    map(F, [H|T]) -> [F(H)|map(F, T)];
    │ │ │ -map(F, [])    -> [].

    It returns the list obtained by applying the function to every argument in the │ │ │ +

    lists:map/2 takes a function of one argument and a list of terms:

    map(F, [H|T]) -> [F(H)|map(F, T)];
    │ │ │ +map(F, [])    -> [].

    It returns the list obtained by applying the function to every argument in the │ │ │ list.

    When a new fun is defined in the shell, the value of the fun is printed as │ │ │ -Fun#<erl_eval>:

    > Double = fun(X) -> 2 * X end.
    │ │ │ +Fun#<erl_eval>:

    > Double = fun(X) -> 2 * X end.
    │ │ │  #Fun<erl_eval.6.72228031>
    │ │ │ -> lists:map(Double, [1,2,3,4,5]).
    │ │ │ -[2,4,6,8,10]

    │ │ │ +> lists:map(Double, [1,2,3,4,5]). │ │ │ +[2,4,6,8,10]

    │ │ │ │ │ │ │ │ │ │ │ │ any │ │ │

    │ │ │ -

    lists:any/2 takes a predicate P of one argument and a list of terms:

    any(Pred, [H|T]) ->
    │ │ │ -    case Pred(H) of
    │ │ │ +

    lists:any/2 takes a predicate P of one argument and a list of terms:

    any(Pred, [H|T]) ->
    │ │ │ +    case Pred(H) of
    │ │ │          true  ->  true;
    │ │ │ -        false ->  any(Pred, T)
    │ │ │ +        false ->  any(Pred, T)
    │ │ │      end;
    │ │ │ -any(Pred, []) ->
    │ │ │ +any(Pred, []) ->
    │ │ │      false.

    A predicate is a function that returns true or false. any is true if │ │ │ there is a term X in the list such that P(X) is true.

    A predicate Big(X) is defined, which is true if its argument is greater that │ │ │ -10:

    > Big =  fun(X) -> if X > 10 -> true; true -> false end end.
    │ │ │ +10:

    > Big =  fun(X) -> if X > 10 -> true; true -> false end end.
    │ │ │  #Fun<erl_eval.6.72228031>
    │ │ │ -> lists:any(Big, [1,2,3,4]).
    │ │ │ +> lists:any(Big, [1,2,3,4]).
    │ │ │  false
    │ │ │ -> lists:any(Big, [1,2,3,12,5]).
    │ │ │ +> lists:any(Big, [1,2,3,12,5]).
    │ │ │  true

    │ │ │ │ │ │ │ │ │ │ │ │ all │ │ │

    │ │ │ -

    lists:all/2 has the same arguments as any:

    all(Pred, [H|T]) ->
    │ │ │ -    case Pred(H) of
    │ │ │ -        true  ->  all(Pred, T);
    │ │ │ +

    lists:all/2 has the same arguments as any:

    all(Pred, [H|T]) ->
    │ │ │ +    case Pred(H) of
    │ │ │ +        true  ->  all(Pred, T);
    │ │ │          false ->  false
    │ │ │      end;
    │ │ │ -all(Pred, []) ->
    │ │ │ -    true.

    It is true if the predicate applied to all elements in the list is true.

    > lists:all(Big, [1,2,3,4,12,6]).
    │ │ │ +all(Pred, []) ->
    │ │ │ +    true.

    It is true if the predicate applied to all elements in the list is true.

    > lists:all(Big, [1,2,3,4,12,6]).
    │ │ │  false
    │ │ │ -> lists:all(Big, [12,13,14,15]).
    │ │ │ +> lists:all(Big, [12,13,14,15]).
    │ │ │  true

    │ │ │ │ │ │ │ │ │ │ │ │ foreach │ │ │

    │ │ │ -

    lists:foreach/2 takes a function of one argument and a list of terms:

    foreach(F, [H|T]) ->
    │ │ │ -    F(H),
    │ │ │ -    foreach(F, T);
    │ │ │ -foreach(F, []) ->
    │ │ │ +

    lists:foreach/2 takes a function of one argument and a list of terms:

    foreach(F, [H|T]) ->
    │ │ │ +    F(H),
    │ │ │ +    foreach(F, T);
    │ │ │ +foreach(F, []) ->
    │ │ │      ok.

    The function is applied to each argument in the list. foreach returns ok. It │ │ │ -is only used for its side-effect:

    > lists:foreach(fun(X) -> io:format("~w~n",[X]) end, [1,2,3,4]).
    │ │ │ +is only used for its side-effect:

    > lists:foreach(fun(X) -> io:format("~w~n",[X]) end, [1,2,3,4]).
    │ │ │  1
    │ │ │  2
    │ │ │  3
    │ │ │  4
    │ │ │  ok

    │ │ │ │ │ │ │ │ │ │ │ │ foldl │ │ │

    │ │ │ -

    lists:foldl/3 takes a function of two arguments, an accumulator and a list:

    foldl(F, Accu, [Hd|Tail]) ->
    │ │ │ -    foldl(F, F(Hd, Accu), Tail);
    │ │ │ -foldl(F, Accu, []) -> Accu.

    The function is called with two arguments. The first argument is the successive │ │ │ +

    lists:foldl/3 takes a function of two arguments, an accumulator and a list:

    foldl(F, Accu, [Hd|Tail]) ->
    │ │ │ +    foldl(F, F(Hd, Accu), Tail);
    │ │ │ +foldl(F, Accu, []) -> Accu.

    The function is called with two arguments. The first argument is the successive │ │ │ elements in the list. The second argument is the accumulator. The function must │ │ │ return a new accumulator, which is used the next time the function is called.

    If you have a list of lists L = ["I","like","Erlang"], then you can sum the │ │ │ -lengths of all the strings in L as follows:

    > L = ["I","like","Erlang"].
    │ │ │ -["I","like","Erlang"]
    │ │ │ -10> lists:foldl(fun(X, Sum) -> length(X) + Sum end, 0, L).
    │ │ │ -11

    lists:foldl/3 works like a while loop in an imperative language:

    L =  ["I","like","Erlang"],
    │ │ │ +lengths of all the strings in L as follows:

    > L = ["I","like","Erlang"].
    │ │ │ +["I","like","Erlang"]
    │ │ │ +10> lists:foldl(fun(X, Sum) -> length(X) + Sum end, 0, L).
    │ │ │ +11

    lists:foldl/3 works like a while loop in an imperative language:

    L =  ["I","like","Erlang"],
    │ │ │  Sum = 0,
    │ │ │ -while( L != []){
    │ │ │ -    Sum += length(head(L)),
    │ │ │ -    L = tail(L)
    │ │ │ +while( L != []){
    │ │ │ +    Sum += length(head(L)),
    │ │ │ +    L = tail(L)
    │ │ │  end

    │ │ │ │ │ │ │ │ │ │ │ │ mapfoldl │ │ │

    │ │ │ -

    lists:mapfoldl/3 simultaneously maps and folds over a list:

    mapfoldl(F, Accu0, [Hd|Tail]) ->
    │ │ │ -    {R,Accu1} = F(Hd, Accu0),
    │ │ │ -    {Rs,Accu2} = mapfoldl(F, Accu1, Tail),
    │ │ │ -    {[R|Rs], Accu2};
    │ │ │ -mapfoldl(F, Accu, []) -> {[], Accu}.

    The following example shows how to change all letters in L to upper case and │ │ │ -then count them.

    First the change to upper case:

    > Upcase =  fun(X) when $a =< X,  X =< $z -> X + $A - $a;
    │ │ │ -(X) -> X
    │ │ │ +

    lists:mapfoldl/3 simultaneously maps and folds over a list:

    mapfoldl(F, Accu0, [Hd|Tail]) ->
    │ │ │ +    {R,Accu1} = F(Hd, Accu0),
    │ │ │ +    {Rs,Accu2} = mapfoldl(F, Accu1, Tail),
    │ │ │ +    {[R|Rs], Accu2};
    │ │ │ +mapfoldl(F, Accu, []) -> {[], Accu}.

    The following example shows how to change all letters in L to upper case and │ │ │ +then count them.

    First the change to upper case:

    > Upcase =  fun(X) when $a =< X,  X =< $z -> X + $A - $a;
    │ │ │ +(X) -> X
    │ │ │  end.
    │ │ │  #Fun<erl_eval.6.72228031>
    │ │ │  > Upcase_word =
    │ │ │ -fun(X) ->
    │ │ │ -lists:map(Upcase, X)
    │ │ │ +fun(X) ->
    │ │ │ +lists:map(Upcase, X)
    │ │ │  end.
    │ │ │  #Fun<erl_eval.6.72228031>
    │ │ │ -> Upcase_word("Erlang").
    │ │ │ +> Upcase_word("Erlang").
    │ │ │  "ERLANG"
    │ │ │ -> lists:map(Upcase_word, L).
    │ │ │ -["I","LIKE","ERLANG"]

    Now, the fold and the map can be done at the same time:

    > lists:mapfoldl(fun(Word, Sum) ->
    │ │ │ -{Upcase_word(Word), Sum + length(Word)}
    │ │ │ -end, 0, L).
    │ │ │ -{["I","LIKE","ERLANG"],11}

    │ │ │ +> lists:map(Upcase_word, L). │ │ │ +["I","LIKE","ERLANG"]

    Now, the fold and the map can be done at the same time:

    > lists:mapfoldl(fun(Word, Sum) ->
    │ │ │ +{Upcase_word(Word), Sum + length(Word)}
    │ │ │ +end, 0, L).
    │ │ │ +{["I","LIKE","ERLANG"],11}

    │ │ │ │ │ │ │ │ │ │ │ │ filter │ │ │

    │ │ │

    lists:filter/2 takes a predicate of one argument and a list and returns all elements │ │ │ -in the list that satisfy the predicate:

    filter(F, [H|T]) ->
    │ │ │ -    case F(H) of
    │ │ │ -        true  -> [H|filter(F, T)];
    │ │ │ -        false -> filter(F, T)
    │ │ │ +in the list that satisfy the predicate:

    filter(F, [H|T]) ->
    │ │ │ +    case F(H) of
    │ │ │ +        true  -> [H|filter(F, T)];
    │ │ │ +        false -> filter(F, T)
    │ │ │      end;
    │ │ │ -filter(F, []) -> [].
    > lists:filter(Big, [500,12,2,45,6,7]).
    │ │ │ -[500,12,45]

    Combining maps and filters enables writing of very succinct code. For example, │ │ │ +filter(F, []) -> [].

    > lists:filter(Big, [500,12,2,45,6,7]).
    │ │ │ +[500,12,45]

    Combining maps and filters enables writing of very succinct code. For example, │ │ │ to define a set difference function diff(L1, L2) to be the difference between │ │ │ -the lists L1 and L2, the code can be written as follows:

    diff(L1, L2) ->
    │ │ │ -    filter(fun(X) -> not member(X, L2) end, L1).

    This gives the list of all elements in L1 that are not contained in L2.

    The AND intersection of the list L1 and L2 is also easily defined:

    intersection(L1,L2) -> filter(fun(X) -> member(X,L1) end, L2).

    │ │ │ +the lists L1 and L2, the code can be written as follows:

    diff(L1, L2) ->
    │ │ │ +    filter(fun(X) -> not member(X, L2) end, L1).

    This gives the list of all elements in L1 that are not contained in L2.

    The AND intersection of the list L1 and L2 is also easily defined:

    intersection(L1,L2) -> filter(fun(X) -> member(X,L1) end, L2).

    │ │ │ │ │ │ │ │ │ │ │ │ takewhile │ │ │

    │ │ │

    lists:takewhile/2 takes elements X from a list L as long as the predicate │ │ │ -P(X) is true:

    takewhile(Pred, [H|T]) ->
    │ │ │ -    case Pred(H) of
    │ │ │ -        true  -> [H|takewhile(Pred, T)];
    │ │ │ -        false -> []
    │ │ │ +P(X) is true:

    takewhile(Pred, [H|T]) ->
    │ │ │ +    case Pred(H) of
    │ │ │ +        true  -> [H|takewhile(Pred, T)];
    │ │ │ +        false -> []
    │ │ │      end;
    │ │ │ -takewhile(Pred, []) ->
    │ │ │ -    [].
    > lists:takewhile(Big, [200,500,45,5,3,45,6]).
    │ │ │ -[200,500,45]

    │ │ │ +takewhile(Pred, []) -> │ │ │ + [].

    > lists:takewhile(Big, [200,500,45,5,3,45,6]).
    │ │ │ +[200,500,45]

    │ │ │ │ │ │ │ │ │ │ │ │ dropwhile │ │ │

    │ │ │ -

    lists:dropwhile/2 is the complement of takewhile:

    dropwhile(Pred, [H|T]) ->
    │ │ │ -    case Pred(H) of
    │ │ │ -        true  -> dropwhile(Pred, T);
    │ │ │ -        false -> [H|T]
    │ │ │ +

    lists:dropwhile/2 is the complement of takewhile:

    dropwhile(Pred, [H|T]) ->
    │ │ │ +    case Pred(H) of
    │ │ │ +        true  -> dropwhile(Pred, T);
    │ │ │ +        false -> [H|T]
    │ │ │      end;
    │ │ │ -dropwhile(Pred, []) ->
    │ │ │ -    [].
    > lists:dropwhile(Big, [200,500,45,5,3,45,6]).
    │ │ │ -[5,3,45,6]

    │ │ │ +dropwhile(Pred, []) -> │ │ │ + [].

    > lists:dropwhile(Big, [200,500,45,5,3,45,6]).
    │ │ │ +[5,3,45,6]

    │ │ │ │ │ │ │ │ │ │ │ │ splitwith │ │ │

    │ │ │

    lists:splitwith/2 splits the list L into the two sublists {L1, L2}, where │ │ │ -L = takewhile(P, L) and L2 = dropwhile(P, L):

    splitwith(Pred, L) ->
    │ │ │ -    splitwith(Pred, L, []).
    │ │ │ +L = takewhile(P, L) and L2 = dropwhile(P, L):

    splitwith(Pred, L) ->
    │ │ │ +    splitwith(Pred, L, []).
    │ │ │  
    │ │ │ -splitwith(Pred, [H|T], L) ->
    │ │ │ -    case Pred(H) of
    │ │ │ -        true  -> splitwith(Pred, T, [H|L]);
    │ │ │ -        false -> {reverse(L), [H|T]}
    │ │ │ +splitwith(Pred, [H|T], L) ->
    │ │ │ +    case Pred(H) of
    │ │ │ +        true  -> splitwith(Pred, T, [H|L]);
    │ │ │ +        false -> {reverse(L), [H|T]}
    │ │ │      end;
    │ │ │ -splitwith(Pred, [], L) ->
    │ │ │ -    {reverse(L), []}.
    > lists:splitwith(Big, [200,500,45,5,3,45,6]).
    │ │ │ -{[200,500,45],[5,3,45,6]}

    │ │ │ +splitwith(Pred, [], L) -> │ │ │ + {reverse(L), []}.

    > lists:splitwith(Big, [200,500,45,5,3,45,6]).
    │ │ │ +{[200,500,45],[5,3,45,6]}

    │ │ │ │ │ │ │ │ │ │ │ │ Funs Returning Funs │ │ │

    │ │ │

    So far, only functions that take funs as arguments have been described. More │ │ │ powerful functions, that themselves return funs, can also be written. The │ │ │ following examples illustrate these type of functions.

    │ │ │ │ │ │ │ │ │ │ │ │ Simple Higher Order Functions │ │ │

    │ │ │

    Adder(X) is a function that given X, returns a new function G such that │ │ │ -G(K) returns K + X:

    > Adder = fun(X) -> fun(Y) -> X + Y end end.
    │ │ │ +G(K) returns K + X:

    > Adder = fun(X) -> fun(Y) -> X + Y end end.
    │ │ │  #Fun<erl_eval.6.72228031>
    │ │ │ -> Add6 = Adder(6).
    │ │ │ +> Add6 = Adder(6).
    │ │ │  #Fun<erl_eval.6.72228031>
    │ │ │ -> Add6(10).
    │ │ │ +> Add6(10).
    │ │ │  16

    │ │ │ │ │ │ │ │ │ │ │ │ Infinite Lists │ │ │

    │ │ │ -

    The idea is to write something like:

    -module(lazy).
    │ │ │ --export([ints_from/1]).
    │ │ │ -ints_from(N) ->
    │ │ │ -    fun() ->
    │ │ │ -            [N|ints_from(N+1)]
    │ │ │ -    end.

    Then proceed as follows:

    > XX = lazy:ints_from(1).
    │ │ │ +

    The idea is to write something like:

    -module(lazy).
    │ │ │ +-export([ints_from/1]).
    │ │ │ +ints_from(N) ->
    │ │ │ +    fun() ->
    │ │ │ +            [N|ints_from(N+1)]
    │ │ │ +    end.

    Then proceed as follows:

    > XX = lazy:ints_from(1).
    │ │ │  #Fun<lazy.0.29874839>
    │ │ │ -> XX().
    │ │ │ -[1|#Fun<lazy.0.29874839>]
    │ │ │ -> hd(XX()).
    │ │ │ +> XX().
    │ │ │ +[1|#Fun<lazy.0.29874839>]
    │ │ │ +> hd(XX()).
    │ │ │  1
    │ │ │ -> Y = tl(XX()).
    │ │ │ +> Y = tl(XX()).
    │ │ │  #Fun<lazy.0.29874839>
    │ │ │ -> hd(Y()).
    │ │ │ +> hd(Y()).
    │ │ │  2

    And so on. This is an example of "lazy embedding".

    │ │ │ │ │ │ │ │ │ │ │ │ Parsing │ │ │

    │ │ │ -

    The following examples show parsers of the following type:

    Parser(Toks) -> {ok, Tree, Toks1} | fail

    Toks is the list of tokens to be parsed. A successful parse returns │ │ │ +

    The following examples show parsers of the following type:

    Parser(Toks) -> {ok, Tree, Toks1} | fail

    Toks is the list of tokens to be parsed. A successful parse returns │ │ │ {ok, Tree, Toks1}.

    • Tree is a parse tree.
    • Toks1 is a tail of Tree that contains symbols encountered after the │ │ │ structure that was correctly parsed.

    An unsuccessful parse returns fail.

    The following example illustrates a simple, functional parser that parses the │ │ │ grammar:

    (a | b) & (c | d)

    The following code defines a function pconst(X) in the module funparse, │ │ │ -which returns a fun that parses a list of tokens:

    pconst(X) ->
    │ │ │ -    fun (T) ->
    │ │ │ +which returns a fun that parses a list of tokens:

    pconst(X) ->
    │ │ │ +    fun (T) ->
    │ │ │         case T of
    │ │ │ -           [X|T1] -> {ok, {const, X}, T1};
    │ │ │ +           [X|T1] -> {ok, {const, X}, T1};
    │ │ │             _      -> fail
    │ │ │         end
    │ │ │ -    end.

    This function can be used as follows:

    > P1 = funparse:pconst(a).
    │ │ │ +    end.

    This function can be used as follows:

    > P1 = funparse:pconst(a).
    │ │ │  #Fun<funparse.0.22674075>
    │ │ │ -> P1([a,b,c]).
    │ │ │ -{ok,{const,a},[b,c]}
    │ │ │ -> P1([x,y,z]).
    │ │ │ +> P1([a,b,c]).
    │ │ │ +{ok,{const,a},[b,c]}
    │ │ │ +> P1([x,y,z]).
    │ │ │  fail

    Next, the two higher order functions pand and por are defined. They combine │ │ │ -primitive parsers to produce more complex parsers.

    First pand:

    pand(P1, P2) ->
    │ │ │ -    fun (T) ->
    │ │ │ -        case P1(T) of
    │ │ │ -            {ok, R1, T1} ->
    │ │ │ -                case P2(T1) of
    │ │ │ -                    {ok, R2, T2} ->
    │ │ │ -                        {ok, {'and', R1, R2}};
    │ │ │ +primitive parsers to produce more complex parsers.

    First pand:

    pand(P1, P2) ->
    │ │ │ +    fun (T) ->
    │ │ │ +        case P1(T) of
    │ │ │ +            {ok, R1, T1} ->
    │ │ │ +                case P2(T1) of
    │ │ │ +                    {ok, R2, T2} ->
    │ │ │ +                        {ok, {'and', R1, R2}};
    │ │ │                      fail ->
    │ │ │                          fail
    │ │ │                  end;
    │ │ │              fail ->
    │ │ │                  fail
    │ │ │          end
    │ │ │      end.

    Given a parser P1 for grammar G1, and a parser P2 for grammar G2, │ │ │ pand(P1, P2) returns a parser for the grammar, which consists of sequences of │ │ │ tokens that satisfy G1, followed by sequences of tokens that satisfy G2.

    por(P1, P2) returns a parser for the language described by the grammar G1 or │ │ │ -G2:

    por(P1, P2) ->
    │ │ │ -    fun (T) ->
    │ │ │ -        case P1(T) of
    │ │ │ -            {ok, R, T1} ->
    │ │ │ -                {ok, {'or',1,R}, T1};
    │ │ │ +G2:

    por(P1, P2) ->
    │ │ │ +    fun (T) ->
    │ │ │ +        case P1(T) of
    │ │ │ +            {ok, R, T1} ->
    │ │ │ +                {ok, {'or',1,R}, T1};
    │ │ │              fail ->
    │ │ │ -                case P2(T) of
    │ │ │ -                    {ok, R1, T1} ->
    │ │ │ -                        {ok, {'or',2,R1}, T1};
    │ │ │ +                case P2(T) of
    │ │ │ +                    {ok, R1, T1} ->
    │ │ │ +                        {ok, {'or',2,R1}, T1};
    │ │ │                      fail ->
    │ │ │                          fail
    │ │ │                  end
    │ │ │          end
    │ │ │      end.

    The original problem was to parse the grammar (a | b) & (c | d). The following │ │ │ -code addresses this problem:

    grammar() ->
    │ │ │ -    pand(
    │ │ │ -         por(pconst(a), pconst(b)),
    │ │ │ -         por(pconst(c), pconst(d))).

    The following code adds a parser interface to the grammar:

    parse(List) ->
    │ │ │ -    (grammar())(List).

    The parser can be tested as follows:

    > funparse:parse([a,c]).
    │ │ │ -{ok,{'and',{'or',1,{const,a}},{'or',1,{const,c}}}}
    │ │ │ -> funparse:parse([a,d]).
    │ │ │ -{ok,{'and',{'or',1,{const,a}},{'or',2,{const,d}}}}
    │ │ │ -> funparse:parse([b,c]).
    │ │ │ -{ok,{'and',{'or',2,{const,b}},{'or',1,{const,c}}}}
    │ │ │ -> funparse:parse([b,d]).
    │ │ │ -{ok,{'and',{'or',2,{const,b}},{'or',2,{const,d}}}}
    │ │ │ -> funparse:parse([a,b]).
    │ │ │ +code addresses this problem:

    grammar() ->
    │ │ │ +    pand(
    │ │ │ +         por(pconst(a), pconst(b)),
    │ │ │ +         por(pconst(c), pconst(d))).

    The following code adds a parser interface to the grammar:

    parse(List) ->
    │ │ │ +    (grammar())(List).

    The parser can be tested as follows:

    > funparse:parse([a,c]).
    │ │ │ +{ok,{'and',{'or',1,{const,a}},{'or',1,{const,c}}}}
    │ │ │ +> funparse:parse([a,d]).
    │ │ │ +{ok,{'and',{'or',1,{const,a}},{'or',2,{const,d}}}}
    │ │ │ +> funparse:parse([b,c]).
    │ │ │ +{ok,{'and',{'or',2,{const,b}},{'or',1,{const,c}}}}
    │ │ │ +> funparse:parse([b,d]).
    │ │ │ +{ok,{'and',{'or',2,{const,b}},{'or',2,{const,d}}}}
    │ │ │ +> funparse:parse([a,b]).
    │ │ │  fail
    │ │ │ │ │ │ │ │ │

    │ │ │

    An example of a simple server written in plain Erlang is provided in │ │ │ Overview. The server can be reimplemented using │ │ │ -gen_server, resulting in this callback module:

    -module(ch3).
    │ │ │ --behaviour(gen_server).
    │ │ │ +gen_server, resulting in this callback module:

    -module(ch3).
    │ │ │ +-behaviour(gen_server).
    │ │ │  
    │ │ │ --export([start_link/0]).
    │ │ │ --export([alloc/0, free/1]).
    │ │ │ --export([init/1, handle_call/3, handle_cast/2]).
    │ │ │ +-export([start_link/0]).
    │ │ │ +-export([alloc/0, free/1]).
    │ │ │ +-export([init/1, handle_call/3, handle_cast/2]).
    │ │ │  
    │ │ │ -start_link() ->
    │ │ │ -    gen_server:start_link({local, ch3}, ch3, [], []).
    │ │ │ +start_link() ->
    │ │ │ +    gen_server:start_link({local, ch3}, ch3, [], []).
    │ │ │  
    │ │ │ -alloc() ->
    │ │ │ -    gen_server:call(ch3, alloc).
    │ │ │ +alloc() ->
    │ │ │ +    gen_server:call(ch3, alloc).
    │ │ │  
    │ │ │ -free(Ch) ->
    │ │ │ -    gen_server:cast(ch3, {free, Ch}).
    │ │ │ +free(Ch) ->
    │ │ │ +    gen_server:cast(ch3, {free, Ch}).
    │ │ │  
    │ │ │ -init(_Args) ->
    │ │ │ -    {ok, channels()}.
    │ │ │ +init(_Args) ->
    │ │ │ +    {ok, channels()}.
    │ │ │  
    │ │ │ -handle_call(alloc, _From, Chs) ->
    │ │ │ -    {Ch, Chs2} = alloc(Chs),
    │ │ │ -    {reply, Ch, Chs2}.
    │ │ │ +handle_call(alloc, _From, Chs) ->
    │ │ │ +    {Ch, Chs2} = alloc(Chs),
    │ │ │ +    {reply, Ch, Chs2}.
    │ │ │  
    │ │ │ -handle_cast({free, Ch}, Chs) ->
    │ │ │ -    Chs2 = free(Ch, Chs),
    │ │ │ -    {noreply, Chs2}.

    The code is explained in the next sections.

    │ │ │ +handle_cast({free, Ch}, Chs) -> │ │ │ + Chs2 = free(Ch, Chs), │ │ │ + {noreply, Chs2}.

    The code is explained in the next sections.

    │ │ │ │ │ │ │ │ │ │ │ │ Starting a Gen_Server │ │ │

    │ │ │

    In the example in the previous section, gen_server is started by calling │ │ │ -ch3:start_link():

    start_link() ->
    │ │ │ -    gen_server:start_link({local, ch3}, ch3, [], []) => {ok, Pid}

    start_link/0 calls function gen_server:start_link/4. This function │ │ │ +ch3:start_link():

    start_link() ->
    │ │ │ +    gen_server:start_link({local, ch3}, ch3, [], []) => {ok, Pid}

    start_link/0 calls function gen_server:start_link/4. This function │ │ │ spawns and links to a new process, a gen_server.

    • The first argument, {local, ch3}, specifies the name. │ │ │ The gen_server is then locally registered as ch3.

      If the name is omitted, the gen_server is not registered. Instead its pid │ │ │ must be used. The name can also be given as {global, Name}, in which case │ │ │ the gen_server is registered using global:register_name/2.

    • The second argument, ch3, is the name of the callback module, which is │ │ │ the module where the callback functions are located.

      The interface functions (start_link/0, alloc/0, and free/1) are located │ │ │ in the same module as the callback functions (init/1, handle_call/3, and │ │ │ handle_cast/2). It is usually good programming practice to have the code │ │ │ corresponding to one process contained in a single module.

    • The third argument, [], is a term that is passed as is to the callback │ │ │ function init. Here, init does not need any indata and ignores the │ │ │ argument.

    • The fourth argument, [], is a list of options. See gen_server │ │ │ for the available options.

    If name registration succeeds, the new gen_server process calls the callback │ │ │ function ch3:init([]). init is expected to return {ok, State}, where │ │ │ State is the internal state of the gen_server. In this case, the state is │ │ │ -the available channels.

    init(_Args) ->
    │ │ │ -    {ok, channels()}.

    gen_server:start_link/4 is synchronous. It does not return until the │ │ │ +the available channels.

    init(_Args) ->
    │ │ │ +    {ok, channels()}.

    gen_server:start_link/4 is synchronous. It does not return until the │ │ │ gen_server has been initialized and is ready to receive requests.

    gen_server:start_link/4 must be used if the gen_server is part of │ │ │ a supervision tree, meaning that it was started by a supervisor. There │ │ │ is another function, gen_server:start/4, to start a standalone │ │ │ gen_server that is not part of a supervision tree.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -221,32 +221,32 @@ │ │ │

    │ │ │

    The synchronous request alloc() is implemented using gen_server:call/2:

    alloc() ->
    │ │ │      gen_server:call(ch3, alloc).

    ch3 is the name of the gen_server and must agree with the name │ │ │ used to start it. alloc is the actual request.

    The request is made into a message and sent to the gen_server. │ │ │ When the request is received, the gen_server calls │ │ │ handle_call(Request, From, State), which is expected to return │ │ │ a tuple {reply,Reply,State1}. Reply is the reply that is to be sent back │ │ │ -to the client, and State1 is a new value for the state of the gen_server.

    handle_call(alloc, _From, Chs) ->
    │ │ │ -    {Ch, Chs2} = alloc(Chs),
    │ │ │ -    {reply, Ch, Chs2}.

    In this case, the reply is the allocated channel Ch and the new state is the │ │ │ +to the client, and State1 is a new value for the state of the gen_server.

    handle_call(alloc, _From, Chs) ->
    │ │ │ +    {Ch, Chs2} = alloc(Chs),
    │ │ │ +    {reply, Ch, Chs2}.

    In this case, the reply is the allocated channel Ch and the new state is the │ │ │ set of remaining available channels Chs2.

    Thus, the call ch3:alloc() returns the allocated channel Ch and the │ │ │ gen_server then waits for new requests, now with an updated list of │ │ │ available channels.

    │ │ │ │ │ │ │ │ │ │ │ │ Asynchronous Requests - Cast │ │ │

    │ │ │ -

    The asynchronous request free(Ch) is implemented using gen_server:cast/2:

    free(Ch) ->
    │ │ │ -    gen_server:cast(ch3, {free, Ch}).

    ch3 is the name of the gen_server. {free, Ch} is the actual request.

    The request is made into a message and sent to the gen_server. │ │ │ +

    The asynchronous request free(Ch) is implemented using gen_server:cast/2:

    free(Ch) ->
    │ │ │ +    gen_server:cast(ch3, {free, Ch}).

    ch3 is the name of the gen_server. {free, Ch} is the actual request.

    The request is made into a message and sent to the gen_server. │ │ │ cast, and thus free, then returns ok.

    When the request is received, the gen_server calls │ │ │ handle_cast(Request, State), which is expected to return a tuple │ │ │ -{noreply,State1}. State1 is a new value for the state of the gen_server.

    handle_cast({free, Ch}, Chs) ->
    │ │ │ -    Chs2 = free(Ch, Chs),
    │ │ │ -    {noreply, Chs2}.

    In this case, the new state is the updated list of available channels Chs2. │ │ │ +{noreply,State1}. State1 is a new value for the state of the gen_server.

    handle_cast({free, Ch}, Chs) ->
    │ │ │ +    Chs2 = free(Ch, Chs),
    │ │ │ +    {noreply, Chs2}.

    In this case, the new state is the updated list of available channels Chs2. │ │ │ The gen_server is now ready for new requests.

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

    │ │ │

    │ │ │ @@ -257,69 +257,69 @@ │ │ │

    │ │ │

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

    If it is necessary to clean up before termination, the shutdown strategy │ │ │ must be a time-out value and the gen_server must be set to trap exit signals │ │ │ in function init. When ordered to shutdown, the gen_server then calls │ │ │ -the callback function terminate(shutdown, State):

    init(Args) ->
    │ │ │ +the callback function terminate(shutdown, State):

    init(Args) ->
    │ │ │      ...,
    │ │ │ -    process_flag(trap_exit, true),
    │ │ │ +    process_flag(trap_exit, true),
    │ │ │      ...,
    │ │ │ -    {ok, State}.
    │ │ │ +    {ok, State}.
    │ │ │  
    │ │ │  ...
    │ │ │  
    │ │ │ -terminate(shutdown, State) ->
    │ │ │ +terminate(shutdown, State) ->
    │ │ │      %% Code for cleaning up here
    │ │ │      ...
    │ │ │      ok.

    │ │ │ │ │ │ │ │ │ │ │ │ Standalone Gen_Servers │ │ │

    │ │ │

    If the gen_server is not part of a supervision tree, a stop function │ │ │ can be useful, for example:

    ...
    │ │ │ -export([stop/0]).
    │ │ │ +export([stop/0]).
    │ │ │  ...
    │ │ │  
    │ │ │ -stop() ->
    │ │ │ -    gen_server:cast(ch3, stop).
    │ │ │ +stop() ->
    │ │ │ +    gen_server:cast(ch3, stop).
    │ │ │  ...
    │ │ │  
    │ │ │ -handle_cast(stop, State) ->
    │ │ │ -    {stop, normal, State};
    │ │ │ -handle_cast({free, Ch}, State) ->
    │ │ │ +handle_cast(stop, State) ->
    │ │ │ +    {stop, normal, State};
    │ │ │ +handle_cast({free, Ch}, State) ->
    │ │ │      ...
    │ │ │  
    │ │ │  ...
    │ │ │  
    │ │ │ -terminate(normal, State) ->
    │ │ │ +terminate(normal, State) ->
    │ │ │      ok.

    The callback function handling the stop request returns a tuple │ │ │ {stop,normal,State1}, where normal specifies that it is │ │ │ a normal termination and State1 is a new value for the state │ │ │ of the gen_server. This causes the gen_server to call │ │ │ terminate(normal, State1) and then it terminates gracefully.

    │ │ │ │ │ │ │ │ │ │ │ │ Handling Other Messages │ │ │

    │ │ │

    If the gen_server is to be able to receive other messages than requests, │ │ │ the callback function handle_info(Info, State) must be implemented │ │ │ to handle them. Examples of other messages are exit messages, │ │ │ if the gen_server is linked to other processes than the supervisor │ │ │ -and it is trapping exit signals.

    handle_info({'EXIT', Pid, Reason}, State) ->
    │ │ │ +and it is trapping exit signals.

    handle_info({'EXIT', Pid, Reason}, State) ->
    │ │ │      %% Code to handle exits here.
    │ │ │      ...
    │ │ │ -    {noreply, State1}.

    The final function to implement is code_change/3:

    code_change(OldVsn, State, Extra) ->
    │ │ │ +    {noreply, State1}.

    The final function to implement is code_change/3:

    code_change(OldVsn, State, Extra) ->
    │ │ │      %% Code to convert state (and more) during code change.
    │ │ │      ...
    │ │ │ -    {ok, NewState}.
    │ │ │ +
    {ok, NewState}.
    │ │ │ │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ Specifying Included Applications │ │ │

    │ │ │

    Which applications to include is defined by the included_applications key in │ │ │ -the .app file:

    {application, prim_app,
    │ │ │ - [{description, "Tree application"},
    │ │ │ -  {vsn, "1"},
    │ │ │ -  {modules, [prim_app_cb, prim_app_sup, prim_app_server]},
    │ │ │ -  {registered, [prim_app_server]},
    │ │ │ -  {included_applications, [incl_app]},
    │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ -  {mod, {prim_app_cb,[]}},
    │ │ │ -  {env, [{file, "/usr/local/log"}]}
    │ │ │ - ]}.

    │ │ │ +the .app file:

    {application, prim_app,
    │ │ │ + [{description, "Tree application"},
    │ │ │ +  {vsn, "1"},
    │ │ │ +  {modules, [prim_app_cb, prim_app_sup, prim_app_server]},
    │ │ │ +  {registered, [prim_app_server]},
    │ │ │ +  {included_applications, [incl_app]},
    │ │ │ +  {applications, [kernel, stdlib, sasl]},
    │ │ │ +  {mod, {prim_app_cb,[]}},
    │ │ │ +  {env, [{file, "/usr/local/log"}]}
    │ │ │ + ]}.

    │ │ │ │ │ │ │ │ │ │ │ │ Synchronizing Processes during Startup │ │ │

    │ │ │

    The supervisor tree of an included application is started as part of the │ │ │ supervisor tree of the including application. If there is a need for │ │ │ synchronization between processes in the including and included applications, │ │ │ this can be achieved by using start phases.

    Start phases are defined by the start_phases key in the .app file as a list │ │ │ of tuples {Phase,PhaseArgs}, where Phase is an atom and PhaseArgs is a │ │ │ term.

    The value of the mod key of the including application must be set to │ │ │ {application_starter,[Module,StartArgs]}, where Module as usual is the │ │ │ application callback module. StartArgs is a term provided as argument to the │ │ │ -callback function Module:start/2:

    {application, prim_app,
    │ │ │ - [{description, "Tree application"},
    │ │ │ -  {vsn, "1"},
    │ │ │ -  {modules, [prim_app_cb, prim_app_sup, prim_app_server]},
    │ │ │ -  {registered, [prim_app_server]},
    │ │ │ -  {included_applications, [incl_app]},
    │ │ │ -  {start_phases, [{init,[]}, {go,[]}]},
    │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ -  {mod, {application_starter,[prim_app_cb,[]]}},
    │ │ │ -  {env, [{file, "/usr/local/log"}]}
    │ │ │ - ]}.
    │ │ │ +callback function Module:start/2:

    {application, prim_app,
    │ │ │ + [{description, "Tree application"},
    │ │ │ +  {vsn, "1"},
    │ │ │ +  {modules, [prim_app_cb, prim_app_sup, prim_app_server]},
    │ │ │ +  {registered, [prim_app_server]},
    │ │ │ +  {included_applications, [incl_app]},
    │ │ │ +  {start_phases, [{init,[]}, {go,[]}]},
    │ │ │ +  {applications, [kernel, stdlib, sasl]},
    │ │ │ +  {mod, {application_starter,[prim_app_cb,[]]}},
    │ │ │ +  {env, [{file, "/usr/local/log"}]}
    │ │ │ + ]}.
    │ │ │  
    │ │ │ -{application, incl_app,
    │ │ │ - [{description, "Included application"},
    │ │ │ -  {vsn, "1"},
    │ │ │ -  {modules, [incl_app_cb, incl_app_sup, incl_app_server]},
    │ │ │ -  {registered, []},
    │ │ │ -  {start_phases, [{go,[]}]},
    │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ -  {mod, {incl_app_cb,[]}}
    │ │ │ - ]}.

    When starting a primary application with included applications, the primary │ │ │ +{application, incl_app, │ │ │ + [{description, "Included application"}, │ │ │ + {vsn, "1"}, │ │ │ + {modules, [incl_app_cb, incl_app_sup, incl_app_server]}, │ │ │ + {registered, []}, │ │ │ + {start_phases, [{go,[]}]}, │ │ │ + {applications, [kernel, stdlib, sasl]}, │ │ │ + {mod, {incl_app_cb,[]}} │ │ │ + ]}.

    When starting a primary application with included applications, the primary │ │ │ application is started the normal way, that is:

    • The application controller creates an application master for the application
    • The application master calls Module:start(normal, StartArgs) to start the │ │ │ top supervisor.

    Then, for the primary application and each included application in top-down, │ │ │ left-to-right order, the application master calls │ │ │ Module:start_phase(Phase, Type, PhaseArgs) for each phase defined for the │ │ │ primary application, in that order. If a phase is not defined for an included │ │ │ application, the function is not called for this phase and application.

    The following requirements apply to the .app file for an included application:

    • The {mod, {Module,StartArgs}} option must be included. This option is used │ │ │ to find the callback module Module of the application. StartArgs is │ │ │ ignored, as Module:start/2 is called only for the primary application.
    • If the included application itself contains included applications, instead the │ │ │ {mod, {application_starter, [Module,StartArgs]}} option must be included.
    • The {start_phases, [{Phase,PhaseArgs}]} option must be included, and the set │ │ │ of specified phases must be a subset of the set of phases specified for the │ │ │ primary application.

    When starting prim_app as defined above, the application controller calls the │ │ │ following callback functions before application:start(prim_app) returns a │ │ │ -value:

    application:start(prim_app)
    │ │ │ - => prim_app_cb:start(normal, [])
    │ │ │ - => prim_app_cb:start_phase(init, normal, [])
    │ │ │ - => prim_app_cb:start_phase(go, normal, [])
    │ │ │ - => incl_app_cb:start_phase(go, normal, [])
    │ │ │ +value:

    application:start(prim_app)
    │ │ │ + => prim_app_cb:start(normal, [])
    │ │ │ + => prim_app_cb:start_phase(init, normal, [])
    │ │ │ + => prim_app_cb:start_phase(go, normal, [])
    │ │ │ + => incl_app_cb:start_phase(go, normal, [])
    │ │ │  ok
    │ │ │
    │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ Frequently Asked Questions │ │ │

    │ │ │
    • Q: So, now I can build Erlang using GCC on Windows?

      A: No, unfortunately not. You'll need Microsoft's Visual C++ │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/list_comprehensions.html │ │ │ @@ -117,33 +117,33 @@ │ │ │ │ │ │

      │ │ │ │ │ │ │ │ │ │ │ │ Simple Examples │ │ │

      │ │ │ -

      This section starts with a simple example, showing a generator and a filter:

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

      This is read as follows: The list of X such that X is taken from the list │ │ │ +

      This section starts with a simple example, showing a generator and a filter:

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

      This is read as follows: The list of X such that X is taken from the list │ │ │ [1,2,a,...] and X is greater than 3.

      The notation X <- [1,2,a,...] is a generator and the expression X > 3 is a │ │ │ filter.

      An additional filter, is_integer(X), can be added to │ │ │ -restrict the result to integers:

      > [X || X <- [1,2,a,3,4,b,5,6], is_integer(X), X > 3].
      │ │ │ -[4,5,6]

      Generators can be combined. For example, the Cartesian product of two lists can │ │ │ -be written as follows:

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

      │ │ │ +restrict the result to integers:

      > [X || X <- [1,2,a,3,4,b,5,6], is_integer(X), X > 3].
      │ │ │ +[4,5,6]

      Generators can be combined. For example, the Cartesian product of two lists can │ │ │ +be written as follows:

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

      │ │ │ │ │ │ │ │ │ │ │ │ Quick Sort │ │ │

      │ │ │ -

      The well-known quick sort routine can be written as follows:

      sort([]) -> [];
      │ │ │ -sort([_] = L) -> L;
      │ │ │ -sort([Pivot|T]) ->
      │ │ │ -    sort([ X || X <- T, X < Pivot]) ++
      │ │ │ -    [Pivot] ++
      │ │ │ -    sort([ X || X <- T, X >= Pivot]).

      The expression [X || X <- T, X < Pivot] is the list of all elements in T │ │ │ +

      The well-known quick sort routine can be written as follows:

      sort([]) -> [];
      │ │ │ +sort([_] = L) -> L;
      │ │ │ +sort([Pivot|T]) ->
      │ │ │ +    sort([ X || X <- T, X < Pivot]) ++
      │ │ │ +    [Pivot] ++
      │ │ │ +    sort([ X || X <- T, X >= Pivot]).

      The expression [X || X <- T, X < Pivot] is the list of all elements in T │ │ │ that are less than Pivot.

      [X || X <- T, X >= Pivot] is the list of all elements in T that are greater │ │ │ than or equal to Pivot.

      With the algorithm above, a list is sorted as follows:

      • A list with zero or one element is trivially sorted.
      • For lists with more than one element:
        1. The first element in the list is isolated as the pivot element.
        2. The remaining list is partitioned into two sublists, such that:
        • The first sublist contains all elements that are smaller than the pivot │ │ │ element.
        • The second sublist contains all elements that are greater than or equal to │ │ │ the pivot element.
        1. The sublists are recursively sorted by the same algorithm and the results │ │ │ are combined, resulting in a list consisting of:
        • All elements from the first sublist, that is all elements smaller than the │ │ │ pivot element, in sorted order.
        • The pivot element.
        • All elements from the second sublist, that is all elements greater than or │ │ │ equal to the pivot element, in sorted order.

      Note

      While the sorting algorithm as shown above serves as a nice example to │ │ │ @@ -151,93 +151,93 @@ │ │ │ lists module contains sorting functions that are implemented in a more │ │ │ efficient way.

      │ │ │ │ │ │ │ │ │ │ │ │ Permutations │ │ │

      │ │ │ -

      The following example generates all permutations of the elements in a list:

      perms([]) -> [[]];
      │ │ │ -perms(L)  -> [[H|T] || H <- L, T <- perms(L--[H])].

      This takes H from L in all possible ways. The result is the set of all lists │ │ │ +

      The following example generates all permutations of the elements in a list:

      perms([]) -> [[]];
      │ │ │ +perms(L)  -> [[H|T] || H <- L, T <- perms(L--[H])].

      This takes H from L in all possible ways. The result is the set of all lists │ │ │ [H|T], where T is the set of all possible permutations of L, with H │ │ │ -removed:

      > perms([b,u,g]).
      │ │ │ -[[b,u,g],[b,g,u],[u,b,g],[u,g,b],[g,b,u],[g,u,b]]

      │ │ │ +removed:

      > perms([b,u,g]).
      │ │ │ +[[b,u,g],[b,g,u],[u,b,g],[u,g,b],[g,b,u],[g,u,b]]

      │ │ │ │ │ │ │ │ │ │ │ │ Pythagorean Triplets │ │ │

      │ │ │

      Pythagorean triplets are sets of integers {A,B,C} such that │ │ │ A**2 + B**2 = C**2.

      The function pyth(N) generates a list of all integers {A,B,C} such that │ │ │ A**2 + B**2 = C**2 and where the sum of the sides is equal to, or less than, │ │ │ -N:

      pyth(N) ->
      │ │ │ -    [ {A,B,C} ||
      │ │ │ -        A <- lists:seq(1,N),
      │ │ │ -        B <- lists:seq(1,N),
      │ │ │ -        C <- lists:seq(1,N),
      │ │ │ +N:

      pyth(N) ->
      │ │ │ +    [ {A,B,C} ||
      │ │ │ +        A <- lists:seq(1,N),
      │ │ │ +        B <- lists:seq(1,N),
      │ │ │ +        C <- lists:seq(1,N),
      │ │ │          A+B+C =< N,
      │ │ │          A*A+B*B == C*C
      │ │ │ -    ].
      > pyth(3).
      │ │ │ -[].
      │ │ │ -> pyth(11).
      │ │ │ -[].
      │ │ │ -> pyth(12).
      │ │ │ -[{3,4,5},{4,3,5}]
      │ │ │ -> pyth(50).
      │ │ │ -[{3,4,5},
      │ │ │ - {4,3,5},
      │ │ │ - {5,12,13},
      │ │ │ - {6,8,10},
      │ │ │ - {8,6,10},
      │ │ │ - {8,15,17},
      │ │ │ - {9,12,15},
      │ │ │ - {12,5,13},
      │ │ │ - {12,9,15},
      │ │ │ - {12,16,20},
      │ │ │ - {15,8,17},
      │ │ │ - {16,12,20}]

      The following code reduces the search space and is more efficient:

      pyth1(N) ->
      │ │ │ -   [{A,B,C} ||
      │ │ │ -       A <- lists:seq(1,N-2),
      │ │ │ -       B <- lists:seq(A+1,N-1),
      │ │ │ -       C <- lists:seq(B+1,N),
      │ │ │ +    ].
      > pyth(3).
      │ │ │ +[].
      │ │ │ +> pyth(11).
      │ │ │ +[].
      │ │ │ +> pyth(12).
      │ │ │ +[{3,4,5},{4,3,5}]
      │ │ │ +> pyth(50).
      │ │ │ +[{3,4,5},
      │ │ │ + {4,3,5},
      │ │ │ + {5,12,13},
      │ │ │ + {6,8,10},
      │ │ │ + {8,6,10},
      │ │ │ + {8,15,17},
      │ │ │ + {9,12,15},
      │ │ │ + {12,5,13},
      │ │ │ + {12,9,15},
      │ │ │ + {12,16,20},
      │ │ │ + {15,8,17},
      │ │ │ + {16,12,20}]

      The following code reduces the search space and is more efficient:

      pyth1(N) ->
      │ │ │ +   [{A,B,C} ||
      │ │ │ +       A <- lists:seq(1,N-2),
      │ │ │ +       B <- lists:seq(A+1,N-1),
      │ │ │ +       C <- lists:seq(B+1,N),
      │ │ │         A+B+C =< N,
      │ │ │ -       A*A+B*B == C*C ].

      │ │ │ + A*A+B*B == C*C ].

      │ │ │ │ │ │ │ │ │ │ │ │ Simplifications With List Comprehensions │ │ │

      │ │ │

      As an example, list comprehensions can be used to simplify some of the functions │ │ │ -in lists.erl:

      append(L)   ->  [X || L1 <- L, X <- L1].
      │ │ │ -map(Fun, L) -> [Fun(X) || X <- L].
      │ │ │ -filter(Pred, L) -> [X || X <- L, Pred(X)].

      │ │ │ +in lists.erl:

      append(L)   ->  [X || L1 <- L, X <- L1].
      │ │ │ +map(Fun, L) -> [Fun(X) || X <- L].
      │ │ │ +filter(Pred, L) -> [X || X <- L, Pred(X)].

      │ │ │ │ │ │ │ │ │ │ │ │ Variable Bindings in List Comprehensions │ │ │

      │ │ │

      The scope rules for variables that occur in list comprehensions are as follows:

      • All variables that occur in a generator pattern are assumed to be "fresh" │ │ │ variables.
      • Any variables that are defined before the list comprehension, and that are │ │ │ used in filters, have the values they had before the list comprehension.
      • Variables cannot be exported from a list comprehension.

      As an example of these rules, suppose you want to write the function select, │ │ │ which selects certain elements from a list of tuples. Suppose you write │ │ │ select(X, L) -> [Y || {X, Y} <- L]. with the intention of extracting all │ │ │ tuples from L, where the first item is X.

      Compiling this gives the following diagnostic:

      ./FileName.erl:Line: Warning: variable 'X' shadowed in generate

      This diagnostic warns that the variable X in the pattern is not the same as │ │ │ -the variable X that occurs in the function head.

      Evaluating select gives the following result:

      > select(b,[{a,1},{b,2},{c,3},{b,7}]).
      │ │ │ -[1,2,3,7]

      This is not the wanted result. To achieve the desired effect, select must be │ │ │ -written as follows:

      select(X, L) ->  [Y || {X1, Y} <- L, X == X1].

      The generator now contains unbound variables and the test has been moved into │ │ │ -the filter.

      This now works as expected:

      > select(b,[{a,1},{b,2},{c,3},{b,7}]).
      │ │ │ -[2,7]

      Also note that a variable in a generator pattern will shadow a variable with the │ │ │ -same name bound in a previous generator pattern. For example:

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

      A consequence of the rules for importing variables into a list comprehensions is │ │ │ +the variable X that occurs in the function head.

      Evaluating select gives the following result:

      > select(b,[{a,1},{b,2},{c,3},{b,7}]).
      │ │ │ +[1,2,3,7]

      This is not the wanted result. To achieve the desired effect, select must be │ │ │ +written as follows:

      select(X, L) ->  [Y || {X1, Y} <- L, X == X1].

      The generator now contains unbound variables and the test has been moved into │ │ │ +the filter.

      This now works as expected:

      > select(b,[{a,1},{b,2},{c,3},{b,7}]).
      │ │ │ +[2,7]

      Also note that a variable in a generator pattern will shadow a variable with the │ │ │ +same name bound in a previous generator pattern. For example:

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

      A consequence of the rules for importing variables into a list comprehensions is │ │ │ that certain pattern matching operations must be moved into the filters and │ │ │ -cannot be written directly in the generators.

      To illustrate this, do not write as follows:

      f(...) ->
      │ │ │ +cannot be written directly in the generators.

      To illustrate this, do not write as follows:

      f(...) ->
      │ │ │      Y = ...
      │ │ │ -    [ Expression || PatternInvolving Y  <- Expr, ...]
      │ │ │ -    ...

      Instead, write as follows:

      f(...) ->
      │ │ │ +    [ Expression || PatternInvolving Y  <- Expr, ...]
      │ │ │ +    ...

      Instead, write as follows:

      f(...) ->
      │ │ │      Y = ...
      │ │ │ -    [ Expression || PatternInvolving Y1  <- Expr, Y == Y1, ...]
      │ │ │ +    [ Expression || PatternInvolving Y1  <- Expr, Y == Y1, ...]
      │ │ │      ...
      │ │ │ │ │ │ │ │ │
      │ │ │
      │ │ │ │ │ │ │ │ │ Creating a List │ │ │ │ │ │

      Lists can only be built starting from the end and attaching list elements at the │ │ │ beginning. If you use the ++ operator as follows, a new list is created that │ │ │ is a copy of the elements in List1, followed by List2:

      List1 ++ List2

      Looking at how lists:append/2 or ++ would be implemented in plain Erlang, │ │ │ -clearly the first list is copied:

      append([H|T], Tail) ->
      │ │ │ -    [H|append(T, Tail)];
      │ │ │ -append([], Tail) ->
      │ │ │ +clearly the first list is copied:

      append([H|T], Tail) ->
      │ │ │ +    [H|append(T, Tail)];
      │ │ │ +append([], Tail) ->
      │ │ │      Tail.

      When recursing and building a list, it is important to ensure that you attach │ │ │ the new elements to the beginning of the list. In this way, you will build one │ │ │ -list, not hundreds or thousands of copies of the growing result list.

      Let us first see how it is not to be done:

      DO NOT

      bad_fib(N) ->
      │ │ │ -    bad_fib(N, 0, 1, []).
      │ │ │ +list, not hundreds or thousands of copies of the growing result list.

      Let us first see how it is not to be done:

      DO NOT

      bad_fib(N) ->
      │ │ │ +    bad_fib(N, 0, 1, []).
      │ │ │  
      │ │ │ -bad_fib(0, _Current, _Next, Fibs) ->
      │ │ │ +bad_fib(0, _Current, _Next, Fibs) ->
      │ │ │      Fibs;
      │ │ │ -bad_fib(N, Current, Next, Fibs) ->
      │ │ │ -    bad_fib(N - 1, Next, Current + Next, Fibs ++ [Current]).

      Here more than one list is built. In each iteration step a new list is created │ │ │ +bad_fib(N, Current, Next, Fibs) -> │ │ │ + bad_fib(N - 1, Next, Current + Next, Fibs ++ [Current]).

      Here more than one list is built. In each iteration step a new list is created │ │ │ that is one element longer than the new previous list.

      To avoid copying the result in each iteration, build the list in reverse order │ │ │ -and reverse the list when you are done:

      DO

      tail_recursive_fib(N) ->
      │ │ │ -    tail_recursive_fib(N, 0, 1, []).
      │ │ │ +and reverse the list when you are done:

      DO

      tail_recursive_fib(N) ->
      │ │ │ +    tail_recursive_fib(N, 0, 1, []).
      │ │ │  
      │ │ │ -tail_recursive_fib(0, _Current, _Next, Fibs) ->
      │ │ │ -    lists:reverse(Fibs);
      │ │ │ -tail_recursive_fib(N, Current, Next, Fibs) ->
      │ │ │ -    tail_recursive_fib(N - 1, Next, Current + Next, [Current|Fibs]).

      │ │ │ +tail_recursive_fib(0, _Current, _Next, Fibs) -> │ │ │ + lists:reverse(Fibs); │ │ │ +tail_recursive_fib(N, Current, Next, Fibs) -> │ │ │ + tail_recursive_fib(N - 1, Next, Current + Next, [Current|Fibs]).

      │ │ │ │ │ │ │ │ │ │ │ │ List Comprehensions │ │ │

      │ │ │ -

      A list comprehension:

      [Expr(E) || E <- List]

      is basically translated to a local function:

      'lc^0'([E|Tail], Expr) ->
      │ │ │ -    [Expr(E)|'lc^0'(Tail, Expr)];
      │ │ │ -'lc^0'([], _Expr) -> [].

      If the result of the list comprehension will obviously not be used, a list │ │ │ -will not be constructed. For example, in this code:

      [io:put_chars(E) || E <- List],
      │ │ │ +

      A list comprehension:

      [Expr(E) || E <- List]

      is basically translated to a local function:

      'lc^0'([E|Tail], Expr) ->
      │ │ │ +    [Expr(E)|'lc^0'(Tail, Expr)];
      │ │ │ +'lc^0'([], _Expr) -> [].

      If the result of the list comprehension will obviously not be used, a list │ │ │ +will not be constructed. For example, in this code:

      [io:put_chars(E) || E <- List],
      │ │ │  ok.

      or in this code:

      case Var of
      │ │ │      ... ->
      │ │ │ -        [io:put_chars(E) || E <- List];
      │ │ │ +        [io:put_chars(E) || E <- List];
      │ │ │      ... ->
      │ │ │  end,
      │ │ │ -some_function(...),

      the value is not assigned to a variable, not passed to another function, and not │ │ │ +some_function(...),

      the value is not assigned to a variable, not passed to another function, and not │ │ │ returned. This means that there is no need to construct a list and the compiler │ │ │ -will simplify the code for the list comprehension to:

      'lc^0'([E|Tail], Expr) ->
      │ │ │ -    Expr(E),
      │ │ │ -    'lc^0'(Tail, Expr);
      │ │ │ -'lc^0'([], _Expr) -> [].

      The compiler also understands that assigning to _ means that the value will │ │ │ -not be used. Therefore, the code in the following example will also be optimized:

      _ = [io:put_chars(E) || E <- List],
      │ │ │ +will simplify the code for the list comprehension to:

      'lc^0'([E|Tail], Expr) ->
      │ │ │ +    Expr(E),
      │ │ │ +    'lc^0'(Tail, Expr);
      │ │ │ +'lc^0'([], _Expr) -> [].

      The compiler also understands that assigning to _ means that the value will │ │ │ +not be used. Therefore, the code in the following example will also be optimized:

      _ = [io:put_chars(E) || E <- List],
      │ │ │  ok.

      │ │ │ │ │ │ │ │ │ │ │ │ Deep and Flat Lists │ │ │

      │ │ │

      lists:flatten/1 builds an entirely new list. It is therefore expensive, and │ │ │ even more expensive than the ++ operator (which copies its left argument, │ │ │ but not its right argument).

      In the following situations it is unnecessary to call lists:flatten/1:

      • When sending data to a port. Ports understand deep lists so there is no reason │ │ │ to flatten the list before sending it to the port.
      • When calling BIFs that accept deep lists, such as │ │ │ list_to_binary/1 or │ │ │ iolist_to_binary/1.
      • When you know that your list is only one level deep. Use lists:append/1 │ │ │ -instead.

      Examples:

      DO

      port_command(Port, DeepList)

      DO NOT

      port_command(Port, lists:flatten(DeepList))

      A common way to send a zero-terminated string to a port is the following:

      DO NOT

      TerminatedStr = String ++ [0],
      │ │ │ -port_command(Port, TerminatedStr)

      Instead:

      DO

      TerminatedStr = [String, 0],
      │ │ │ -port_command(Port, TerminatedStr)

      DO

      1> lists:append([[1], [2], [3]]).
      │ │ │ -[1,2,3]

      DO NOT

      1> lists:flatten([[1], [2], [3]]).
      │ │ │ -[1,2,3]

      │ │ │ +instead.

    Examples:

    DO

    port_command(Port, DeepList)

    DO NOT

    port_command(Port, lists:flatten(DeepList))

    A common way to send a zero-terminated string to a port is the following:

    DO NOT

    TerminatedStr = String ++ [0],
    │ │ │ +port_command(Port, TerminatedStr)

    Instead:

    DO

    TerminatedStr = [String, 0],
    │ │ │ +port_command(Port, TerminatedStr)

    DO

    1> lists:append([[1], [2], [3]]).
    │ │ │ +[1,2,3]

    DO NOT

    1> lists:flatten([[1], [2], [3]]).
    │ │ │ +[1,2,3]

    │ │ │ │ │ │ │ │ │ │ │ │ Recursive List Functions │ │ │

    │ │ │

    There are two basic ways to write a function that traverses a list and │ │ │ produces a new list.

    The first way is writing a body-recursive function:

    %% Add 42 to each integer in the list.
    │ │ │ -add_42_body([H|T]) ->
    │ │ │ -    [H + 42 | add_42_body(T)];
    │ │ │ -add_42_body([]) ->
    │ │ │ -    [].

    The second way is writing a tail-recursive function:

    %% Add 42 to each integer in the list.
    │ │ │ -add_42_tail(List) ->
    │ │ │ -    add_42_tail(List, []).
    │ │ │ -
    │ │ │ -add_42_tail([H|T], Acc) ->
    │ │ │ -    add_42_tail(T, [H + 42 | Acc]);
    │ │ │ -add_42_tail([], Acc) ->
    │ │ │ -    lists:reverse(Acc).

    In early version of Erlang the tail-recursive function would typically │ │ │ +add_42_body([H|T]) -> │ │ │ + [H + 42 | add_42_body(T)]; │ │ │ +add_42_body([]) -> │ │ │ + [].

    The second way is writing a tail-recursive function:

    %% Add 42 to each integer in the list.
    │ │ │ +add_42_tail(List) ->
    │ │ │ +    add_42_tail(List, []).
    │ │ │ +
    │ │ │ +add_42_tail([H|T], Acc) ->
    │ │ │ +    add_42_tail(T, [H + 42 | Acc]);
    │ │ │ +add_42_tail([], Acc) ->
    │ │ │ +    lists:reverse(Acc).

    In early version of Erlang the tail-recursive function would typically │ │ │ be more efficient. In modern versions of Erlang, there is usually not │ │ │ much difference in performance between a body-recursive list function and │ │ │ tail-recursive function that reverses the list at the end. Therefore, │ │ │ concentrate on writing beautiful code and forget about the performance │ │ │ of your list functions. In the time-critical parts of your code, │ │ │ measure before rewriting your code.

    For a thorough discussion about tail and body recursion, see │ │ │ Erlang's Tail Recursion is Not a Silver Bullet.

    Note

    This section is about list functions that construct lists. A tail-recursive │ │ │ function that does not construct a list runs in constant space, while the │ │ │ corresponding body-recursive function uses stack space proportional to the │ │ │ length of the list.

    For example, a function that sums a list of integers, is not to be written as │ │ │ -follows:

    DO NOT

    recursive_sum([H|T]) -> H+recursive_sum(T);
    │ │ │ -recursive_sum([])    -> 0.

    Instead:

    DO

    sum(L) -> sum(L, 0).
    │ │ │ +follows:

    DO NOT

    recursive_sum([H|T]) -> H+recursive_sum(T);
    │ │ │ +recursive_sum([])    -> 0.

    Instead:

    DO

    sum(L) -> sum(L, 0).
    │ │ │  
    │ │ │ -sum([H|T], Sum) -> sum(T, Sum + H);
    │ │ │ -sum([], Sum)    -> Sum.
    │ │ │ +
    sum([H|T], Sum) -> sum(T, Sum + H); │ │ │ +sum([], Sum) -> Sum.
    │ │ │ │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ File Inclusion │ │ │

    │ │ │ -

    A file can be included as follows:

    -include(File).
    │ │ │ --include_lib(File).

    File, a string, is to point out a file. The contents of this file are included │ │ │ +

    A file can be included as follows:

    -include(File).
    │ │ │ +-include_lib(File).

    File, a string, is to point out a file. The contents of this file are included │ │ │ as is, at the position of the directive.

    Include files are typically used for record and macro definitions that are │ │ │ shared by several modules. It is recommended to use the file name extension │ │ │ .hrl for include files.

    File can start with a path component $VAR, for some string VAR. If that is │ │ │ the case, the value of the environment variable VAR as returned by │ │ │ os:getenv(VAR) is substituted for $VAR. If os:getenv(VAR) returns false, │ │ │ $VAR is left as is.

    If the filename File is absolute (possibly after variable substitution), the │ │ │ include file with that name is included. Otherwise, the specified file is │ │ │ searched for in the following directories, and in this order:

    1. The current working directory
    2. The directory where the module is being compiled
    3. The directories given by the include option

    For details, see erlc in ERTS and │ │ │ -compile in Compiler.

    Examples:

    -include("my_records.hrl").
    │ │ │ --include("incdir/my_records.hrl").
    │ │ │ --include("/home/user/proj/my_records.hrl").
    │ │ │ --include("$PROJ_ROOT/my_records.hrl").

    include_lib is similar to include, but is not to point out an absolute file. │ │ │ +compile in Compiler.

    Examples:

    -include("my_records.hrl").
    │ │ │ +-include("incdir/my_records.hrl").
    │ │ │ +-include("/home/user/proj/my_records.hrl").
    │ │ │ +-include("$PROJ_ROOT/my_records.hrl").

    include_lib is similar to include, but is not to point out an absolute file. │ │ │ Instead, the first path component (possibly after variable substitution) is │ │ │ -assumed to be the name of an application.

    Example:

    -include_lib("kernel/include/file.hrl").

    The code server uses code:lib_dir(kernel) to find the directory of the current │ │ │ +assumed to be the name of an application.

    Example:

    -include_lib("kernel/include/file.hrl").

    The code server uses code:lib_dir(kernel) to find the directory of the current │ │ │ (latest) version of Kernel, and then the subdirectory include is searched for │ │ │ the file file.hrl.

    │ │ │ │ │ │ │ │ │ │ │ │ Defining and Using Macros │ │ │

    │ │ │ -

    A macro is defined as follows:

    -define(Const, Replacement).
    │ │ │ --define(Func(Var1,...,VarN), Replacement).

    A macro definition can be placed anywhere among the attributes and function │ │ │ +

    A macro is defined as follows:

    -define(Const, Replacement).
    │ │ │ +-define(Func(Var1,...,VarN), Replacement).

    A macro definition can be placed anywhere among the attributes and function │ │ │ declarations of a module, but the definition must come before any usage of the │ │ │ macro.

    If a macro is used in several modules, it is recommended that the macro │ │ │ definition is placed in an include file.

    A macro is used as follows:

    ?Const
    │ │ │  ?Func(Arg1,...,ArgN)

    Macros are expanded during compilation. A simple macro ?Const is replaced with │ │ │ -Replacement.

    Example:

    -define(TIMEOUT, 200).
    │ │ │ +Replacement.

    Example:

    -define(TIMEOUT, 200).
    │ │ │  ...
    │ │ │ -call(Request) ->
    │ │ │ -    server:call(refserver, Request, ?TIMEOUT).

    This is expanded to:

    call(Request) ->
    │ │ │ -    server:call(refserver, Request, 200).

    A macro ?Func(Arg1,...,ArgN) is replaced with Replacement, where all │ │ │ +call(Request) -> │ │ │ + server:call(refserver, Request, ?TIMEOUT).

    This is expanded to:

    call(Request) ->
    │ │ │ +    server:call(refserver, Request, 200).

    A macro ?Func(Arg1,...,ArgN) is replaced with Replacement, where all │ │ │ occurrences of a variable Var from the macro definition are replaced with the │ │ │ -corresponding argument Arg.

    Example:

    -define(MACRO1(X, Y), {a, X, b, Y}).
    │ │ │ +corresponding argument Arg.

    Example:

    -define(MACRO1(X, Y), {a, X, b, Y}).
    │ │ │  ...
    │ │ │ -bar(X) ->
    │ │ │ -    ?MACRO1(a, b),
    │ │ │ -    ?MACRO1(X, 123)

    This is expanded to:

    bar(X) ->
    │ │ │ -    {a,a,b,b},
    │ │ │ -    {a,X,b,123}.

    It is good programming practice, but not mandatory, to ensure that a macro │ │ │ +bar(X) -> │ │ │ + ?MACRO1(a, b), │ │ │ + ?MACRO1(X, 123)

    This is expanded to:

    bar(X) ->
    │ │ │ +    {a,a,b,b},
    │ │ │ +    {a,X,b,123}.

    It is good programming practice, but not mandatory, to ensure that a macro │ │ │ definition is a valid Erlang syntactic form.

    To view the result of macro expansion, a module can be compiled with the 'P' │ │ │ option. compile:file(File, ['P']). This produces a listing of the parsed code │ │ │ after preprocessing and parse transforms, in the file File.P.

    │ │ │ │ │ │ │ │ │ │ │ │ Predefined Macros │ │ │ @@ -185,29 +185,29 @@ │ │ │ │ │ │ │ │ │ Macros Overloading │ │ │

    │ │ │

    It is possible to overload macros, except for predefined macros. An overloaded │ │ │ macro has more than one definition, each with a different number of arguments.

    Change

    Support for overloading of macros was added in Erlang 5.7.5/OTP R13B04.

    A macro ?Func(Arg1,...,ArgN) with a (possibly empty) list of arguments results │ │ │ in an error message if there is at least one definition of Func with │ │ │ -arguments, but none with N arguments.

    Assuming these definitions:

    -define(F0(), c).
    │ │ │ --define(F1(A), A).
    │ │ │ --define(C, m:f).

    the following does not work:

    f0() ->
    │ │ │ +arguments, but none with N arguments.

    Assuming these definitions:

    -define(F0(), c).
    │ │ │ +-define(F1(A), A).
    │ │ │ +-define(C, m:f).

    the following does not work:

    f0() ->
    │ │ │      ?F0. % No, an empty list of arguments expected.
    │ │ │  
    │ │ │ -f1(A) ->
    │ │ │ -    ?F1(A, A). % No, exactly one argument expected.

    On the other hand,

    f() ->
    │ │ │ -    ?C().

    is expanded to

    f() ->
    │ │ │ -    m:f().

    │ │ │ +f1(A) -> │ │ │ + ?F1(A, A). % No, exactly one argument expected.

    On the other hand,

    f() ->
    │ │ │ +    ?C().

    is expanded to

    f() ->
    │ │ │ +    m:f().

    │ │ │ │ │ │ │ │ │ │ │ │ Removing a macro definition │ │ │

    │ │ │ -

    A definition of macro can be removed as follows:

    -undef(Macro).

    │ │ │ +

    A definition of macro can be removed as follows:

    -undef(Macro).

    │ │ │ │ │ │ │ │ │ │ │ │ Conditional Compilation │ │ │

    │ │ │

    The following macro directives support conditional compilation:

    • -ifdef(Macro). - Evaluate the following lines only if Macro is │ │ │ defined.

    • -ifndef(Macro). - Evaluate the following lines only if Macro is not │ │ │ @@ -219,43 +219,43 @@ │ │ │ true, and the Condition evaluates to true, the lines following the elif │ │ │ are evaluated instead.

    • -endif. - Specifies the end of a series of control flow directives.

    Note

    Macro directives cannot be used inside functions.

    Syntactically, the Condition in if and elif must be a │ │ │ guard expression. Other constructs (such as │ │ │ a case expression) result in a compilation error.

    As opposed to the standard guard expressions, an expression in an if and │ │ │ elif also supports calling the psuedo-function defined(Name), which tests │ │ │ whether the Name argument is the name of a previously defined macro. │ │ │ defined(Name) evaluates to true if the macro is defined and false │ │ │ -otherwise. An attempt to call other functions results in a compilation error.

    Example:

    -module(m).
    │ │ │ +otherwise. An attempt to call other functions results in a compilation error.

    Example:

    -module(m).
    │ │ │  ...
    │ │ │  
    │ │ │ --ifdef(debug).
    │ │ │ --define(LOG(X), io:format("{~p,~p}: ~p~n", [?MODULE,?LINE,X])).
    │ │ │ +-ifdef(debug).
    │ │ │ +-define(LOG(X), io:format("{~p,~p}: ~p~n", [?MODULE,?LINE,X])).
    │ │ │  -else.
    │ │ │ --define(LOG(X), true).
    │ │ │ +-define(LOG(X), true).
    │ │ │  -endif.
    │ │ │  
    │ │ │  ...

    When trace output is desired, debug is to be defined when the module m is │ │ │ compiled:

    % erlc -Ddebug m.erl
    │ │ │  
    │ │ │  or
    │ │ │  
    │ │ │ -1> c(m, {d, debug}).
    │ │ │ -{ok,m}

    ?LOG(Arg) is then expanded to a call to io:format/2 and provide the user │ │ │ -with some simple trace output.

    Example:

    -module(m)
    │ │ │ +1> c(m, {d, debug}).
    │ │ │ +{ok,m}

    ?LOG(Arg) is then expanded to a call to io:format/2 and provide the user │ │ │ +with some simple trace output.

    Example:

    -module(m)
    │ │ │  ...
    │ │ │ --if(?OTP_RELEASE >= 25).
    │ │ │ +-if(?OTP_RELEASE >= 25).
    │ │ │  %% Code that will work in OTP 25 or higher
    │ │ │ --elif(?OTP_RELEASE >= 26).
    │ │ │ +-elif(?OTP_RELEASE >= 26).
    │ │ │  %% Code that will work in OTP 26 or higher
    │ │ │  -else.
    │ │ │  %% Code that will work in OTP 24 or lower.
    │ │ │  -endif.
    │ │ │  ...

    This code uses the OTP_RELEASE macro to conditionally select code depending on │ │ │ -release.

    Example:

    -module(m)
    │ │ │ +release.

    Example:

    -module(m)
    │ │ │  ...
    │ │ │ --if(?OTP_RELEASE >= 26 andalso defined(debug)).
    │ │ │ +-if(?OTP_RELEASE >= 26 andalso defined(debug)).
    │ │ │  %% Debugging code that requires OTP 26 or later.
    │ │ │  -else.
    │ │ │  %% Non-debug code that works in any release.
    │ │ │  -endif.
    │ │ │  ...

    This code uses the OTP_RELEASE macro and defined(debug) to compile debug │ │ │ code only for OTP 26 or later.

    │ │ │ │ │ │ @@ -270,44 +270,44 @@ │ │ │ used. In practice this means it should appear before any -export(..) or record │ │ │ definitions.

    │ │ │ │ │ │ │ │ │ │ │ │ -error() and -warning() directives │ │ │

    │ │ │ -

    The directive -error(Term) causes a compilation error.

    Example:

    -module(t).
    │ │ │ --export([version/0]).
    │ │ │ +

    The directive -error(Term) causes a compilation error.

    Example:

    -module(t).
    │ │ │ +-export([version/0]).
    │ │ │  
    │ │ │ --ifdef(VERSION).
    │ │ │ -version() -> ?VERSION.
    │ │ │ +-ifdef(VERSION).
    │ │ │ +version() -> ?VERSION.
    │ │ │  -else.
    │ │ │ --error("Macro VERSION must be defined.").
    │ │ │ -version() -> "".
    │ │ │ +-error("Macro VERSION must be defined.").
    │ │ │ +version() -> "".
    │ │ │  -endif.

    The error message will look like this:

    % erlc t.erl
    │ │ │ -t.erl:7: -error("Macro VERSION must be defined.").

    The directive -warning(Term) causes a compilation warning.

    Example:

    -module(t).
    │ │ │ --export([version/0]).
    │ │ │ +t.erl:7: -error("Macro VERSION must be defined.").

    The directive -warning(Term) causes a compilation warning.

    Example:

    -module(t).
    │ │ │ +-export([version/0]).
    │ │ │  
    │ │ │ --ifndef(VERSION).
    │ │ │ --warning("Macro VERSION not defined -- using default version.").
    │ │ │ --define(VERSION, "0").
    │ │ │ +-ifndef(VERSION).
    │ │ │ +-warning("Macro VERSION not defined -- using default version.").
    │ │ │ +-define(VERSION, "0").
    │ │ │  -endif.
    │ │ │ -version() -> ?VERSION.

    The warning message will look like this:

    % erlc t.erl
    │ │ │ +version() -> ?VERSION.

    The warning message will look like this:

    % erlc t.erl
    │ │ │  t.erl:5: Warning: -warning("Macro VERSION not defined -- using default version.").

    Change

    The -error() and -warning() directives were added in Erlang/OTP 19.

    │ │ │ │ │ │ │ │ │ │ │ │ Stringifying Macro Arguments │ │ │

    │ │ │

    The construction ??Arg, where Arg is a macro argument, is expanded to a │ │ │ string containing the tokens of the argument. This is similar to the #arg │ │ │ -stringifying construction in C.

    Example:

    -define(TESTCALL(Call), io:format("Call ~s: ~w~n", [??Call, Call])).
    │ │ │ +stringifying construction in C.

    Example:

    -define(TESTCALL(Call), io:format("Call ~s: ~w~n", [??Call, Call])).
    │ │ │  
    │ │ │ -?TESTCALL(myfunction(1,2)),
    │ │ │ -?TESTCALL(you:function(2,1)).

    results in

    io:format("Call ~s: ~w~n",["myfunction ( 1 , 2 )",myfunction(1,2)]),
    │ │ │ -io:format("Call ~s: ~w~n",["you : function ( 2 , 1 )",you:function(2,1)]).

    That is, a trace output, with both the function called and the resulting value.

    │ │ │ +
    ?TESTCALL(myfunction(1,2)), │ │ │ +?TESTCALL(you:function(2,1)).

    results in

    io:format("Call ~s: ~w~n",["myfunction ( 1 , 2 )",myfunction(1,2)]),
    │ │ │ +io:format("Call ~s: ~w~n",["you : function ( 2 , 1 )",you:function(2,1)]).

    That is, a trace output, with both the function called and the resulting value.

    │ │ │

    │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │
  • maps:get/3 function. If there are default │ │ │ values, sharing of keys between different instances of the map will be less │ │ │ effective, and it is not possible to match multiple elements having default │ │ │ values in one go.

  • To avoid having to deal with a map that may lack some keys, maps:merge/2 can │ │ │ -efficiently add multiple default values. For example:

    DefaultMap = #{shoe_size => 42, editor => emacs},
    │ │ │ -MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)
  • │ │ │ +efficiently add multiple default values. For example:

    DefaultMap = #{shoe_size => 42, editor => emacs},
    │ │ │ +MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)

    │ │ │ │ │ │ │ │ │ │ │ │ Using Maps as Dictionaries │ │ │

    │ │ │

    Using a map as a dictionary implies the following usage pattern:

    • Keys are usually variables not known at compile-time.
    • There can be any number of elements in the map.
    • Usually, no more than one element is looked up or updated at once.

    Given that usage pattern, the difference in performance between using the map │ │ │ syntax and the maps module is usually small. Therefore, which one to use is │ │ │ @@ -167,18 +167,18 @@ │ │ │ choice.

    │ │ │ │ │ │ │ │ │ │ │ │ Using Maps as Sets │ │ │

    │ │ │

    Starting in OTP 24, the sets module has an option to represent sets as maps. │ │ │ -Examples:

    1> sets:new([{version,2}]).
    │ │ │ -#{}
    │ │ │ -2> sets:from_list([x,y,z], [{version,2}]).
    │ │ │ -#{x => [],y => [],z => []}

    sets backed by maps is generally the most efficient set representation, with a │ │ │ +Examples:

    1> sets:new([{version,2}]).
    │ │ │ +#{}
    │ │ │ +2> sets:from_list([x,y,z], [{version,2}]).
    │ │ │ +#{x => [],y => [],z => []}

    sets backed by maps is generally the most efficient set representation, with a │ │ │ few possible exceptions:

    • ordsets:intersection/2 can be more efficient than sets:intersection/2. If │ │ │ the intersection operation is frequently used and operations that operate on a │ │ │ single element in a set (such as is_element/2) are avoided, ordsets can │ │ │ be a better choice than sets.
    • If the intersection operation is frequently used and operations that operate │ │ │ on a single element in a set (such as is_element/2) must also be efficient, │ │ │ gb_sets can potentially be a better choice than sets.
    • If the elements of the set are integers in a fairly compact range, the set can │ │ │ be represented as an integer where each bit represents an element in the set. │ │ │ @@ -203,18 +203,18 @@ │ │ │ for the runtime system).

    • N - The number of elements in the map.

    • Keys - A tuple with keys of the map: {Key1,...,KeyN}. The keys are │ │ │ sorted.

    • Value1 - The value corresponding to the first key in the key tuple.

    • ValueN - The value corresponding to the last key in the key tuple.

    As an example, let us look at how the map #{a => foo, z => bar} is │ │ │ represented:

    01234
    FLATMAP2{a,z}foobar

    Table: #{a => foo, z => bar}

    Let us update the map: M#{q => baz}. The map now looks like this:

    012345
    FLATMAP3{a,q,z}foobazbar

    Table: #{a => foo, q => baz, z => bar}

    Finally, change the value of one element: M#{z := bird}. The map now looks │ │ │ like this:

    012345
    FLATMAP3{a,q,z}foobazbird

    Table: #{a => foo, q => baz, z => bird}

    When the value for an existing key is updated, the key tuple is not updated, │ │ │ allowing the key tuple to be shared with other instances of the map that have │ │ │ the same keys. In fact, the key tuple can be shared between all maps with the │ │ │ same keys with some care. To arrange that, define a function that returns a map. │ │ │ -For example:

    new() ->
    │ │ │ -    #{a => default, b => default, c => default}.

    Defined like this, the key tuple {a,b,c} will be a global literal. To ensure │ │ │ +For example:

    new() ->
    │ │ │ +    #{a => default, b => default, c => default}.

    Defined like this, the key tuple {a,b,c} will be a global literal. To ensure │ │ │ that the key tuple is shared when creating an instance of the map, always call │ │ │ -new() and modify the returned map:

        (SOME_MODULE:new())#{a := 42}.

    Using the map syntax with small maps is particularly efficient. As long as the │ │ │ +new() and modify the returned map:

        (SOME_MODULE:new())#{a := 42}.

    Using the map syntax with small maps is particularly efficient. As long as the │ │ │ keys are known at compile-time, the map is updated in one go, making the time to │ │ │ update a map essentially constant regardless of the number of keys updated. The │ │ │ same goes for matching. (When the keys are variables, one or more of the keys │ │ │ could be identical, so the operations need to be performed sequentially from │ │ │ left to right.)

    The memory size for a small map is the size of all keys and values plus 5 words. │ │ │ See Memory for more information about memory sizes.

    │ │ │ │ │ │ @@ -241,21 +241,21 @@ │ │ │ │ │ │ │ │ │ │ │ │ Using the Map Syntax │ │ │

    │ │ │

    Using the map syntax is usually slightly more efficient than using the │ │ │ corresponding function in the maps module.

    The gain in efficiency for the map syntax is more noticeable for the following │ │ │ -operations that can only be achieved using the map syntax:

    • Matching multiple literal keys
    • Updating multiple literal keys
    • Adding multiple literal keys to a map

    For example:

    DO

    Map = Map1#{x := X, y := Y, z := Z}

    DO NOT

    Map2 = maps:update(x, X, Map1),
    │ │ │ -Map3 = maps:update(y, Y, Map2),
    │ │ │ -Map = maps:update(z, Z, Map3)

    If the map is a small map, the first example runs roughly three times as fast.

    Note that for variable keys, the elements are updated sequentially from left to │ │ │ -right. For example, given the following update with variable keys:

    Map = Map1#{Key1 := X, Key2 := Y, Key3 := Z}

    the compiler rewrites it like this to ensure that the updates are applied from │ │ │ -left to right:

    Map2 = Map1#{Key1 := X},
    │ │ │ -Map3 = Map2#{Key2 := Y},
    │ │ │ -Map = Map3#{Key3 := Z}

    If a key is known to exist in a map, using the := operator is slightly more │ │ │ +operations that can only be achieved using the map syntax:

    • Matching multiple literal keys
    • Updating multiple literal keys
    • Adding multiple literal keys to a map

    For example:

    DO

    Map = Map1#{x := X, y := Y, z := Z}

    DO NOT

    Map2 = maps:update(x, X, Map1),
    │ │ │ +Map3 = maps:update(y, Y, Map2),
    │ │ │ +Map = maps:update(z, Z, Map3)

    If the map is a small map, the first example runs roughly three times as fast.

    Note that for variable keys, the elements are updated sequentially from left to │ │ │ +right. For example, given the following update with variable keys:

    Map = Map1#{Key1 := X, Key2 := Y, Key3 := Z}

    the compiler rewrites it like this to ensure that the updates are applied from │ │ │ +left to right:

    Map2 = Map1#{Key1 := X},
    │ │ │ +Map3 = Map2#{Key2 := Y},
    │ │ │ +Map = Map3#{Key3 := Z}

    If a key is known to exist in a map, using the := operator is slightly more │ │ │ efficient than using the => operator for a small map.

    │ │ │ │ │ │ │ │ │ │ │ │ Using the Functions in the maps Module │ │ │

    │ │ │

    Here follows some notes about most of the functions in the maps module. For │ │ │ @@ -306,23 +306,23 @@ │ │ │ │ │ │ │ │ │ │ │ │ maps:get/3 │ │ │ │ │ │

    As an optimization, the compiler will rewrite a call to maps:get/3 to Erlang │ │ │ code similar to the following:

    Result = case Map of
    │ │ │ -             #{Key := Value} -> Value;
    │ │ │ -             #{} -> Default
    │ │ │ +             #{Key := Value} -> Value;
    │ │ │ +             #{} -> Default
    │ │ │           end

    This is reasonably efficient, but if a small map is used as an alternative to │ │ │ using a record it is often better not to rely on default values as it prevents │ │ │ sharing of keys, which may in the end use more memory than what you save from │ │ │ not storing default values in the map.

    If default values are nevertheless required, instead of calling maps:get/3 │ │ │ multiple times, consider putting the default values in a map and merging that │ │ │ -map with the other map:

    DefaultMap = #{Key1 => Value2, Key2 => Value2, ..., KeyN => ValueN},
    │ │ │ -MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)

    This helps share keys between the default map and the one you applied defaults │ │ │ +map with the other map:

    DefaultMap = #{Key1 => Value2, Key2 => Value2, ..., KeyN => ValueN},
    │ │ │ +MapWithDefaultsApplied = maps:merge(DefaultMap, OtherMap)

    This helps share keys between the default map and the one you applied defaults │ │ │ to, as long as the default map contains all the keys that will ever be used │ │ │ and not just the ones with default values. Whether this is faster than calling │ │ │ maps:get/3 multiple times depends on the size of the map and the number of │ │ │ default values.

    Change

    Before OTP 26.0 maps:get/3 was implemented by calling the function instead │ │ │ of rewriting it as an Erlang expression. It is now slightly faster but can no │ │ │ longer be traced.

    │ │ │ │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/modules.html │ │ │ @@ -118,20 +118,20 @@ │ │ │

    │ │ │ │ │ │ │ │ │ │ │ │ Module Syntax │ │ │

    │ │ │

    Erlang code is divided into modules. A module consists of a sequence of │ │ │ -attributes and function declarations, each terminated by a period (.).

    Example:

    -module(m).          % module attribute
    │ │ │ --export([fact/1]).   % module attribute
    │ │ │ +attributes and function declarations, each terminated by a period (.).

    Example:

    -module(m).          % module attribute
    │ │ │ +-export([fact/1]).   % module attribute
    │ │ │  
    │ │ │ -fact(N) when N>0 ->  % beginning of function declaration
    │ │ │ -    N * fact(N-1);   %  |
    │ │ │ -fact(0) ->           %  |
    │ │ │ +fact(N) when N>0 ->  % beginning of function declaration
    │ │ │ +    N * fact(N-1);   %  |
    │ │ │ +fact(0) ->           %  |
    │ │ │      1.               % end of function declaration

    For a description of function declarations, see │ │ │ Function Declaration Syntax.

    │ │ │ │ │ │ │ │ │ │ │ │ Module Attributes │ │ │

    │ │ │ @@ -176,71 +176,71 @@ │ │ │ meaning.

    │ │ │ │ │ │ │ │ │ │ │ │ Behaviour Module Attribute │ │ │

    │ │ │

    It is possible to specify that the module is the callback module for a │ │ │ -behaviour:

    -behaviour(Behaviour).

    The atom Behaviour gives the name of the behaviour, which can be a │ │ │ +behaviour:

    -behaviour(Behaviour).

    The atom Behaviour gives the name of the behaviour, which can be a │ │ │ user-defined behaviour or one of the following OTP standard behaviours:

    • gen_server
    • gen_statem
    • gen_event
    • supervisor

    The spelling behavior is also accepted.

    The callback functions of the module can be specified either directly by the │ │ │ -exported function behaviour_info/1:

    behaviour_info(callbacks) -> Callbacks.

    or by a -callback attribute for each callback function:

    -callback Name(Arguments) -> Result.

    Here, Arguments is a list of zero or more arguments. The -callback attribute │ │ │ +exported function behaviour_info/1:

    behaviour_info(callbacks) -> Callbacks.

    or by a -callback attribute for each callback function:

    -callback Name(Arguments) -> Result.

    Here, Arguments is a list of zero or more arguments. The -callback attribute │ │ │ is to be preferred since the extra type information can be used by tools to │ │ │ produce documentation or find discrepancies.

    Read more about behaviours and callback modules in │ │ │ OTP Design Principles.

    │ │ │ │ │ │ │ │ │ │ │ │ Record Definitions │ │ │

    │ │ │ -

    The same syntax as for module attributes is used for record definitions:

    -record(Record, Fields).

    Record definitions are allowed anywhere in a module, also among the function │ │ │ +

    The same syntax as for module attributes is used for record definitions:

    -record(Record, Fields).

    Record definitions are allowed anywhere in a module, also among the function │ │ │ declarations. Read more in Records.

    │ │ │ │ │ │ │ │ │ │ │ │ Preprocessor │ │ │

    │ │ │

    The same syntax as for module attributes is used by the preprocessor, which │ │ │ -supports file inclusion, macros, and conditional compilation:

    -include("SomeFile.hrl").
    │ │ │ --define(Macro, Replacement).

    Read more in Preprocessor.

    │ │ │ +supports file inclusion, macros, and conditional compilation:

    -include("SomeFile.hrl").
    │ │ │ +-define(Macro, Replacement).

    Read more in Preprocessor.

    │ │ │ │ │ │ │ │ │ │ │ │ Setting File and Line │ │ │

    │ │ │

    The same syntax as for module attributes is used for changing the pre-defined │ │ │ -macros ?FILE and ?LINE:

    -file(File, Line).

    This attribute is used by tools, such as Yecc, to inform the compiler that the │ │ │ +macros ?FILE and ?LINE:

    -file(File, Line).

    This attribute is used by tools, such as Yecc, to inform the compiler that the │ │ │ source program is generated by another tool. It also indicates the │ │ │ correspondence of source files to lines of the original user-written file, from │ │ │ which the source program is produced.

    │ │ │ │ │ │ │ │ │ │ │ │ Types and function specifications │ │ │

    │ │ │

    A similar syntax as for module attributes is used for specifying types and │ │ │ -function specifications:

    -type my_type() :: atom() | integer().
    │ │ │ --spec my_function(integer()) -> integer().

    Read more in Types and Function specifications.

    The description is based on │ │ │ +function specifications:

    -type my_type() :: atom() | integer().
    │ │ │ +-spec my_function(integer()) -> integer().

    Read more in Types and Function specifications.

    The description is based on │ │ │ EEP8 - Types and function specifications, │ │ │ which is not to be further updated.

    │ │ │ │ │ │ │ │ │ │ │ │ Documentation attributes │ │ │

    │ │ │

    The module attribute -doc(Documentation) is used to provide user documentation │ │ │ -for a function/type/callback:

    -doc("Example documentation").
    │ │ │ -example() -> ok.

    The attribute should be placed just before the entity it documents.The │ │ │ +for a function/type/callback:

    -doc("Example documentation").
    │ │ │ +example() -> ok.

    The attribute should be placed just before the entity it documents.The │ │ │ parenthesis are optional around Documentation. The allowed values for │ │ │ Documentation are:

    • literal string or │ │ │ utf-8 encoded binary string - The string │ │ │ documenting the entity. Any literal string is allowed, so both │ │ │ triple quoted strings and │ │ │ sigils that translate to literal strings can be used. │ │ │ -The following examples are equivalent:

      -doc("Example \"docs\"").
      │ │ │ --doc(<<"Example \"docs\""/utf8>>).
      │ │ │ +The following examples are equivalent:

      -doc("Example \"docs\"").
      │ │ │ +-doc(<<"Example \"docs\""/utf8>>).
      │ │ │  -doc ~S/Example "docs"/.
      │ │ │  -doc """
      │ │ │     Example "docs"
      │ │ │     """
      │ │ │  -doc ~B|Example "docs"|.

      For clarity it is recommended to use either normal "strings" or triple │ │ │ quoted strings for documentation attributes.

    • {file, file:name/0 } - Read the contents of filename and use │ │ │ that as the documentation string.

    • false - Set the current entity as hidden, that is, it should not be │ │ │ @@ -253,15 +253,15 @@ │ │ │ │ │ │ │ │ │ │ │ │ The feature directive │ │ │

    │ │ │

    While not a module attribute, but rather a directive (since it might affect │ │ │ syntax), there is the -feature(..) directive used for enabling and disabling │ │ │ -features.

    The syntax is similar to that of an attribute, but has two arguments:

    -feature(FeatureName, enable | disable).

    Note that the feature directive can only appear │ │ │ +features.

    The syntax is similar to that of an attribute, but has two arguments:

    -feature(FeatureName, enable | disable).

    Note that the feature directive can only appear │ │ │ in a prefix of the module.

    │ │ │ │ │ │ │ │ │ │ │ │ Comments │ │ │

    │ │ │

    Comments can be placed anywhere in a module except within strings and │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/nif.html │ │ │ @@ -133,26 +133,26 @@ │ │ │ Erlang Program │ │ │ │ │ │

    Even if all functions of a module are NIFs, an Erlang module is still needed for │ │ │ two reasons:

    • The NIF library must be explicitly loaded by Erlang code in the same module.
    • All NIFs of a module must have an Erlang implementation as well.

    Normally these are minimal stub implementations that throw an exception. But │ │ │ they can also be used as fallback implementations for functions that do not have │ │ │ native implementations on some architectures.

    NIF libraries are loaded by calling erlang:load_nif/2, with the name of the │ │ │ shared library as argument. The second argument can be any term that will be │ │ │ -passed on to the library and used for initialization:

    -module(complex6).
    │ │ │ --export([foo/1, bar/1]).
    │ │ │ --nifs([foo/1, bar/1]).
    │ │ │ --on_load(init/0).
    │ │ │ -
    │ │ │ -init() ->
    │ │ │ -    ok = erlang:load_nif("./complex6_nif", 0).
    │ │ │ -
    │ │ │ -foo(_X) ->
    │ │ │ -    erlang:nif_error(nif_library_not_loaded).
    │ │ │ -bar(_Y) ->
    │ │ │ -    erlang:nif_error(nif_library_not_loaded).

    Here, the directive on_load is used to get function init to be automatically │ │ │ +passed on to the library and used for initialization:

    -module(complex6).
    │ │ │ +-export([foo/1, bar/1]).
    │ │ │ +-nifs([foo/1, bar/1]).
    │ │ │ +-on_load(init/0).
    │ │ │ +
    │ │ │ +init() ->
    │ │ │ +    ok = erlang:load_nif("./complex6_nif", 0).
    │ │ │ +
    │ │ │ +foo(_X) ->
    │ │ │ +    erlang:nif_error(nif_library_not_loaded).
    │ │ │ +bar(_Y) ->
    │ │ │ +    erlang:nif_error(nif_library_not_loaded).

    Here, the directive on_load is used to get function init to be automatically │ │ │ called when the module is loaded. If init returns anything other than ok, │ │ │ such when the loading of the NIF library fails in this example, the module is │ │ │ unloaded and calls to functions within it, fail.

    Loading the NIF library overrides the stub implementations and cause calls to │ │ │ foo and bar to be dispatched to the NIF implementations instead.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -165,43 +165,43 @@ │ │ │ is a shared module, not a program, no main function is to be present.

    The function arguments passed to a NIF appears in an array argv, with argc │ │ │ as the length of the array, and thus the arity of the function. The Nth argument │ │ │ of the function can be accessed as argv[N-1]. NIFs also take an environment │ │ │ argument that serves as an opaque handle that is needed to be passed on to most │ │ │ API functions. The environment contains information about the calling Erlang │ │ │ process:

    #include <erl_nif.h>
    │ │ │  
    │ │ │ -extern int foo(int x);
    │ │ │ -extern int bar(int y);
    │ │ │ +extern int foo(int x);
    │ │ │ +extern int bar(int y);
    │ │ │  
    │ │ │ -static ERL_NIF_TERM foo_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
    │ │ │ -{
    │ │ │ +static ERL_NIF_TERM foo_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
    │ │ │ +{
    │ │ │      int x, ret;
    │ │ │ -    if (!enif_get_int(env, argv[0], &x)) {
    │ │ │ -	return enif_make_badarg(env);
    │ │ │ -    }
    │ │ │ -    ret = foo(x);
    │ │ │ -    return enif_make_int(env, ret);
    │ │ │ -}
    │ │ │ +    if (!enif_get_int(env, argv[0], &x)) {
    │ │ │ +	return enif_make_badarg(env);
    │ │ │ +    }
    │ │ │ +    ret = foo(x);
    │ │ │ +    return enif_make_int(env, ret);
    │ │ │ +}
    │ │ │  
    │ │ │ -static ERL_NIF_TERM bar_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
    │ │ │ -{
    │ │ │ +static ERL_NIF_TERM bar_nif(ErlNifEnv* env, int argc, const ERL_NIF_TERM argv[])
    │ │ │ +{
    │ │ │      int y, ret;
    │ │ │ -    if (!enif_get_int(env, argv[0], &y)) {
    │ │ │ -	return enif_make_badarg(env);
    │ │ │ -    }
    │ │ │ -    ret = bar(y);
    │ │ │ -    return enif_make_int(env, ret);
    │ │ │ -}
    │ │ │ -
    │ │ │ -static ErlNifFunc nif_funcs[] = {
    │ │ │ -    {"foo", 1, foo_nif},
    │ │ │ -    {"bar", 1, bar_nif}
    │ │ │ -};
    │ │ │ +    if (!enif_get_int(env, argv[0], &y)) {
    │ │ │ +	return enif_make_badarg(env);
    │ │ │ +    }
    │ │ │ +    ret = bar(y);
    │ │ │ +    return enif_make_int(env, ret);
    │ │ │ +}
    │ │ │ +
    │ │ │ +static ErlNifFunc nif_funcs[] = {
    │ │ │ +    {"foo", 1, foo_nif},
    │ │ │ +    {"bar", 1, bar_nif}
    │ │ │ +};
    │ │ │  
    │ │ │ -ERL_NIF_INIT(complex6, nif_funcs, NULL, NULL, NULL, NULL)

    Here, ERL_NIF_INIT has the following arguments:

    • The first argument must be the name of the Erlang module as a C-identifier. It │ │ │ +ERL_NIF_INIT(complex6, nif_funcs, NULL, NULL, NULL, NULL)

    Here, ERL_NIF_INIT has the following arguments:

    • The first argument must be the name of the Erlang module as a C-identifier. It │ │ │ will be stringified by the macro.
    • The second argument is the array of ErlNifFunc structures containing name, │ │ │ arity, and function pointer of each NIF.
    • The remaining arguments are pointers to callback functions that can be used to │ │ │ initialize the library. They are not used in this simple example, hence they │ │ │ are all set to NULL.

    Function arguments and return values are represented as values of type │ │ │ ERL_NIF_TERM. Here, functions like enif_get_int and enif_make_int are used │ │ │ to convert between Erlang term and C-type. If the function argument argv[0] is │ │ │ not an integer, enif_get_int returns false, in which case it returns by │ │ │ @@ -209,23 +209,23 @@ │ │ │ │ │ │ │ │ │ │ │ │ Running the Example │ │ │ │ │ │

    Step 1. Compile the C code:

    unix> gcc -o complex6_nif.so -fpic -shared complex.c complex6_nif.c
    │ │ │  windows> cl -LD -MD -Fe complex6_nif.dll complex.c complex6_nif.c

    Step 2: Start Erlang and compile the Erlang code:

    > erl
    │ │ │ -Erlang R13B04 (erts-5.7.5) [64-bit] [smp:4:4] [rq:4] [async-threads:0] [kernel-poll:false]
    │ │ │ +Erlang R13B04 (erts-5.7.5) [64-bit] [smp:4:4] [rq:4] [async-threads:0] [kernel-poll:false]
    │ │ │  
    │ │ │ -Eshell V5.7.5  (abort with ^G)
    │ │ │ -1> c(complex6).
    │ │ │ -{ok,complex6}

    Step 3: Run the example:

    3> complex6:foo(3).
    │ │ │ +Eshell V5.7.5  (abort with ^G)
    │ │ │ +1> c(complex6).
    │ │ │ +{ok,complex6}

    Step 3: Run the example:

    3> complex6:foo(3).
    │ │ │  4
    │ │ │ -4> complex6:bar(5).
    │ │ │ +4> complex6:bar(5).
    │ │ │  10
    │ │ │ -5> complex6:foo("not an integer").
    │ │ │ +5> complex6:foo("not an integer").
    │ │ │  ** exception error: bad argument
    │ │ │       in function  complex6:foo/1
    │ │ │          called as comlpex6:foo("not an integer")
    │ │ │ │ │ │ │ │ │
    │ │ │
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/opaques.html │ │ │ @@ -122,24 +122,24 @@ │ │ │ Opaque Type Aliases │ │ │ │ │ │

    The main use case for opacity in Erlang is to hide the implementation of a data │ │ │ type, enabling evolving the API while minimizing the risk of breaking consumers. │ │ │ The runtime does not check opacity. Dialyzer provides some opacity-checking, but │ │ │ the rest is up to convention.

    This document explains what Erlang opacity is (and the trade-offs involved) via │ │ │ the example of the sets:set() data type. This type was │ │ │ -defined in the sets module like this:

    -opaque set(Element) :: #set{segs :: segs(Element)}.

    OTP 24 changed the definition to the following in │ │ │ -this commit.

    -opaque set(Element) :: #set{segs :: segs(Element)} | #{Element => ?VALUE}.

    And this change was safer and more backwards-compatible than if the type had │ │ │ +defined in the sets module like this:

    -opaque set(Element) :: #set{segs :: segs(Element)}.

    OTP 24 changed the definition to the following in │ │ │ +this commit.

    -opaque set(Element) :: #set{segs :: segs(Element)} | #{Element => ?VALUE}.

    And this change was safer and more backwards-compatible than if the type had │ │ │ been defined with -type instead of -opaque. Here is why: when a module │ │ │ defines an -opaque, the contract is that only the defining module should rely │ │ │ on the definition of the type: no other modules should rely on the definition.

    This means that code that pattern-matched on set as a record/tuple technically │ │ │ broke the contract, and opted in to being potentially broken when the definition │ │ │ of set() changed. Before OTP 24, this code printed ok. In OTP 24 it may │ │ │ -error:

    case sets:new() of
    │ │ │ -    Set when is_tuple(Set) ->
    │ │ │ -        io:format("ok")
    │ │ │ +error:

    case sets:new() of
    │ │ │ +    Set when is_tuple(Set) ->
    │ │ │ +        io:format("ok")
    │ │ │  end.

    When working with an opaque defined in another module, here are some │ │ │ recommendations:

    • Don't examine the underlying type using pattern-matching, guards, or functions │ │ │ that reveal the type, such as tuple_size/1 .
    • Instead, use functions provided by the module for working with the type. For │ │ │ example, sets module provides sets:new/0, sets:add_element/2, │ │ │ sets:is_element/2, and so on.
    • sets:set(a) is a subtype of sets:set(a | b) and not the │ │ │ other way around. Generally, you can rely on the property that the_opaque(T) │ │ │ is a subtype of the_opaque(U) when T is a subtype of U.

    When defining your own opaques, here are some recommendations:

    • Since consumers are expected to not rely on the definition of the opaque type, │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/otp-patch-apply.html │ │ │ @@ -201,15 +201,15 @@ │ │ │ │ │ │ Sanity check │ │ │ │ │ │

      The application dependencies can be checked using the Erlang shell. │ │ │ Application dependencies are verified among installed applications by │ │ │ otp_patch_apply, but these are not necessarily those actually loaded. │ │ │ By calling system_information:sanity_check() one can validate │ │ │ -dependencies among applications actually loaded.

      1> system_information:sanity_check().
      │ │ │ +dependencies among applications actually loaded.

      1> system_information:sanity_check().
      │ │ │  ok

      Please take a look at the reference of sanity_check() for more │ │ │ information.

      │ │ │
    │ │ │ │ │ │
    │ │ │
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/patterns.html │ │ │ @@ -128,18 +128,18 @@ │ │ │ succeeds, any unbound variables in the pattern become bound. If the matching │ │ │ fails, an exception is raised.

    Examples:

    1> X.
    │ │ │  ** 1:1: variable 'X' is unbound **
    │ │ │  2> X = 2.
    │ │ │  2
    │ │ │  3> X + 1.
    │ │ │  3
    │ │ │ -4> {X, Y} = {1, 2}.
    │ │ │ +4> {X, Y} = {1, 2}.
    │ │ │  ** exception error: no match of right hand side value {1,2}
    │ │ │ -5> {X, Y} = {2, 3}.
    │ │ │ -{2,3}
    │ │ │ +5> {X, Y} = {2, 3}.
    │ │ │ +{2,3}
    │ │ │  6> Y.
    │ │ │  3
    │ │ │
    │ │ │ │ │ │
    │ │ │
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/prog_ex_records.html │ │ │ @@ -122,105 +122,105 @@ │ │ │ Records and Tuples │ │ │ │ │ │

    The main advantage of using records rather than tuples is that fields in a │ │ │ record are accessed by name, whereas fields in a tuple are accessed by position. │ │ │ To illustrate these differences, suppose that you want to represent a person │ │ │ with the tuple {Name, Address, Phone}.

    To write functions that manipulate this data, remember the following:

    • The Name field is the first element of the tuple.
    • The Address field is the second element.
    • The Phone field is the third element.

    For example, to extract data from a variable P that contains such a tuple, you │ │ │ can write the following code and then use pattern matching to extract the │ │ │ -relevant fields:

    Name = element(1, P),
    │ │ │ -Address = element(2, P),
    │ │ │ +relevant fields:

    Name = element(1, P),
    │ │ │ +Address = element(2, P),
    │ │ │  ...

    Such code is difficult to read and understand, and errors occur if the numbering │ │ │ of the elements in the tuple is wrong. If the data representation of the fields │ │ │ is changed, by re-ordering, adding, or removing fields, all references to the │ │ │ person tuple must be checked and possibly modified.

    Records allow references to the fields by name, instead of by position. In the │ │ │ -following example, a record instead of a tuple is used to store the data:

    -record(person, {name, phone, address}).

    This enables references to the fields of the record by name. For example, if P │ │ │ +following example, a record instead of a tuple is used to store the data:

    -record(person, {name, phone, address}).

    This enables references to the fields of the record by name. For example, if P │ │ │ is a variable whose value is a person record, the following code access the │ │ │ name and address fields of the records:

    Name = P#person.name,
    │ │ │  Address = P#person.address,
    │ │ │ -...

    Internally, records are represented using tagged tuples:

    {person, Name, Phone, Address}

    │ │ │ +...

    Internally, records are represented using tagged tuples:

    {person, Name, Phone, Address}

    │ │ │ │ │ │ │ │ │ │ │ │ Defining a Record │ │ │

    │ │ │

    This following definition of a person is used in several examples in this │ │ │ section. Three fields are included, name, phone, and address. The default │ │ │ values for name and phone is "" and [], respectively. The default value for │ │ │ address is the atom undefined, since no default value is supplied for this │ │ │ -field:

    -record(person, {name = "", phone = [], address}).

    The record must be defined in the shell to enable use of the record syntax in │ │ │ -the examples:

    > rd(person, {name = "", phone = [], address}).
    │ │ │ +field:

    -record(person, {name = "", phone = [], address}).

    The record must be defined in the shell to enable use of the record syntax in │ │ │ +the examples:

    > rd(person, {name = "", phone = [], address}).
    │ │ │  person

    This is because record definitions are only available at compile time, not at │ │ │ runtime. For details on records in the shell, see the shell manual page in │ │ │ STDLIB.

    │ │ │ │ │ │ │ │ │ │ │ │ Creating a Record │ │ │

    │ │ │ -

    A new person record is created as follows:

    > #person{phone=[0,8,2,3,4,3,1,2], name="Robert"}.
    │ │ │ -#person{name = "Robert",phone = [0,8,2,3,4,3,1,2],address = undefined}

    As the address field was omitted, its default value is used.

    From Erlang 5.1/OTP R8B, a value to all fields in a record can be set with the │ │ │ -special field _. _ means "all fields not explicitly specified".

    Example:

    > #person{name = "Jakob", _ = '_'}.
    │ │ │ -#person{name = "Jakob",phone = '_',address = '_'}

    It is primarily intended to be used in ets:match/2 and │ │ │ +

    A new person record is created as follows:

    > #person{phone=[0,8,2,3,4,3,1,2], name="Robert"}.
    │ │ │ +#person{name = "Robert",phone = [0,8,2,3,4,3,1,2],address = undefined}

    As the address field was omitted, its default value is used.

    From Erlang 5.1/OTP R8B, a value to all fields in a record can be set with the │ │ │ +special field _. _ means "all fields not explicitly specified".

    Example:

    > #person{name = "Jakob", _ = '_'}.
    │ │ │ +#person{name = "Jakob",phone = '_',address = '_'}

    It is primarily intended to be used in ets:match/2 and │ │ │ mnesia:match_object/3, to set record fields to the atom '_'. (This is a │ │ │ wildcard in ets:match/2.)

    │ │ │ │ │ │ │ │ │ │ │ │ Accessing a Record Field │ │ │

    │ │ │ -

    The following example shows how to access a record field:

    > P = #person{name = "Joe", phone = [0,8,2,3,4,3,1,2]}.
    │ │ │ -#person{name = "Joe",phone = [0,8,2,3,4,3,1,2],address = undefined}
    │ │ │ +

    The following example shows how to access a record field:

    > P = #person{name = "Joe", phone = [0,8,2,3,4,3,1,2]}.
    │ │ │ +#person{name = "Joe",phone = [0,8,2,3,4,3,1,2],address = undefined}
    │ │ │  > P#person.name.
    │ │ │  "Joe"

    │ │ │ │ │ │ │ │ │ │ │ │ Updating a Record │ │ │

    │ │ │ -

    The following example shows how to update a record:

    > P1 = #person{name="Joe", phone=[1,2,3], address="A street"}.
    │ │ │ -#person{name = "Joe",phone = [1,2,3],address = "A street"}
    │ │ │ -> P2 = P1#person{name="Robert"}.
    │ │ │ -#person{name = "Robert",phone = [1,2,3],address = "A street"}

    │ │ │ +

    The following example shows how to update a record:

    > P1 = #person{name="Joe", phone=[1,2,3], address="A street"}.
    │ │ │ +#person{name = "Joe",phone = [1,2,3],address = "A street"}
    │ │ │ +> P2 = P1#person{name="Robert"}.
    │ │ │ +#person{name = "Robert",phone = [1,2,3],address = "A street"}

    │ │ │ │ │ │ │ │ │ │ │ │ Type Testing │ │ │

    │ │ │

    The following example shows that the guard succeeds if P is record of type │ │ │ -person:

    foo(P) when is_record(P, person) -> a_person;
    │ │ │ -foo(_) -> not_a_person.

    │ │ │ +person:

    foo(P) when is_record(P, person) -> a_person;
    │ │ │ +foo(_) -> not_a_person.

    │ │ │ │ │ │ │ │ │ │ │ │ Pattern Matching │ │ │

    │ │ │

    Matching can be used in combination with records, as shown in the following │ │ │ -example:

    > P3 = #person{name="Joe", phone=[0,0,7], address="A street"}.
    │ │ │ -#person{name = "Joe",phone = [0,0,7],address = "A street"}
    │ │ │ -> #person{name = Name} = P3, Name.
    │ │ │ +example:

    > P3 = #person{name="Joe", phone=[0,0,7], address="A street"}.
    │ │ │ +#person{name = "Joe",phone = [0,0,7],address = "A street"}
    │ │ │ +> #person{name = Name} = P3, Name.
    │ │ │  "Joe"

    The following function takes a list of person records and searches for the │ │ │ -phone number of a person with a particular name:

    find_phone([#person{name=Name, phone=Phone} | _], Name) ->
    │ │ │ -    {found,  Phone};
    │ │ │ -find_phone([_| T], Name) ->
    │ │ │ -    find_phone(T, Name);
    │ │ │ -find_phone([], Name) ->
    │ │ │ +phone number of a person with a particular name:

    find_phone([#person{name=Name, phone=Phone} | _], Name) ->
    │ │ │ +    {found,  Phone};
    │ │ │ +find_phone([_| T], Name) ->
    │ │ │ +    find_phone(T, Name);
    │ │ │ +find_phone([], Name) ->
    │ │ │      not_found.

    The fields referred to in the pattern can be given in any order.

    │ │ │ │ │ │ │ │ │ │ │ │ Nested Records │ │ │

    │ │ │

    The value of a field in a record can be an instance of a record. Retrieval of │ │ │ nested data can be done stepwise, or in a single step, as shown in the following │ │ │ -example:

    -record(name, {first = "Robert", last = "Ericsson"}).
    │ │ │ --record(person, {name = #name{}, phone}).
    │ │ │ +example:

    -record(name, {first = "Robert", last = "Ericsson"}).
    │ │ │ +-record(person, {name = #name{}, phone}).
    │ │ │  
    │ │ │ -demo() ->
    │ │ │ -  P = #person{name= #name{first="Robert",last="Virding"}, phone=123},
    │ │ │ -  First = (P#person.name)#name.first.

    Here, demo() evaluates to "Robert".

    │ │ │ +demo() -> │ │ │ + P = #person{name= #name{first="Robert",last="Virding"}, phone=123}, │ │ │ + First = (P#person.name)#name.first.

    Here, demo() evaluates to "Robert".

    │ │ │ │ │ │ │ │ │ │ │ │ A Longer Example │ │ │

    │ │ │

    Comments are embedded in the following example:

    %% File: person.hrl
    │ │ │  
    │ │ │ @@ -230,48 +230,48 @@
    │ │ │  %%    name:  A string (default is undefined).
    │ │ │  %%    age:   An integer (default is undefined).
    │ │ │  %%    phone: A list of integers (default is []).
    │ │ │  %%    dict:  A dictionary containing various information
    │ │ │  %%           about the person.
    │ │ │  %%           A {Key, Value} list (default is the empty list).
    │ │ │  %%------------------------------------------------------------
    │ │ │ --record(person, {name, age, phone = [], dict = []}).
    -module(person).
    │ │ │ --include("person.hrl").
    │ │ │ --compile(export_all). % For test purposes only.
    │ │ │ +-record(person, {name, age, phone = [], dict = []}).
    -module(person).
    │ │ │ +-include("person.hrl").
    │ │ │ +-compile(export_all). % For test purposes only.
    │ │ │  
    │ │ │  %% This creates an instance of a person.
    │ │ │  %%   Note: The phone number is not supplied so the
    │ │ │  %%         default value [] will be used.
    │ │ │  
    │ │ │ -make_hacker_without_phone(Name, Age) ->
    │ │ │ -   #person{name = Name, age = Age,
    │ │ │ -           dict = [{computer_knowledge, excellent},
    │ │ │ -                   {drinks, coke}]}.
    │ │ │ +make_hacker_without_phone(Name, Age) ->
    │ │ │ +   #person{name = Name, age = Age,
    │ │ │ +           dict = [{computer_knowledge, excellent},
    │ │ │ +                   {drinks, coke}]}.
    │ │ │  
    │ │ │  %% This demonstrates matching in arguments
    │ │ │  
    │ │ │ -print(#person{name = Name, age = Age,
    │ │ │ -              phone = Phone, dict = Dict}) ->
    │ │ │ -  io:format("Name: ~s, Age: ~w, Phone: ~w ~n"
    │ │ │ -            "Dictionary: ~w.~n", [Name, Age, Phone, Dict]).
    │ │ │ +print(#person{name = Name, age = Age,
    │ │ │ +              phone = Phone, dict = Dict}) ->
    │ │ │ +  io:format("Name: ~s, Age: ~w, Phone: ~w ~n"
    │ │ │ +            "Dictionary: ~w.~n", [Name, Age, Phone, Dict]).
    │ │ │  
    │ │ │  %% Demonstrates type testing, selector, updating.
    │ │ │  
    │ │ │ -birthday(P) when is_record(P, person) ->
    │ │ │ -   P#person{age = P#person.age + 1}.
    │ │ │ +birthday(P) when is_record(P, person) ->
    │ │ │ +   P#person{age = P#person.age + 1}.
    │ │ │  
    │ │ │ -register_two_hackers() ->
    │ │ │ -   Hacker1 = make_hacker_without_phone("Joe", 29),
    │ │ │ -   OldHacker = birthday(Hacker1),
    │ │ │ +register_two_hackers() ->
    │ │ │ +   Hacker1 = make_hacker_without_phone("Joe", 29),
    │ │ │ +   OldHacker = birthday(Hacker1),
    │ │ │     % The central_register_server should have
    │ │ │     % an interface function for this.
    │ │ │ -   central_register_server ! {register_person, Hacker1},
    │ │ │ -   central_register_server ! {register_person,
    │ │ │ -             OldHacker#person{name = "Robert",
    │ │ │ -                              phone = [0,8,3,2,4,5,3,1]}}.
    │ │ │ +
    central_register_server ! {register_person, Hacker1}, │ │ │ + central_register_server ! {register_person, │ │ │ + OldHacker#person{name = "Robert", │ │ │ + phone = [0,8,3,2,4,5,3,1]}}.
    │ │ │
    │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ Header Files │ │ │

    │ │ │

    As shown above, some files have extension .hrl. These are header files that │ │ │ -are included in the .erl files by:

    -include("File_Name").

    for example:

    -include("mess_interface.hrl").

    In the case above the file is fetched from the same directory as all the other │ │ │ +are included in the .erl files by:

    -include("File_Name").

    for example:

    -include("mess_interface.hrl").

    In the case above the file is fetched from the same directory as all the other │ │ │ files in the messenger example. (manual).

    .hrl files can contain any valid Erlang code but are most often used for record │ │ │ and macro definitions.

    │ │ │ │ │ │ │ │ │ │ │ │ Records │ │ │

    │ │ │ -

    A record is defined as:

    -record(name_of_record,{field_name1, field_name2, field_name3, ......}).

    For example:

    -record(message_to,{to_name, message}).

    This is equivalent to:

    {message_to, To_Name, Message}

    Creating a record is best illustrated by an example:

    #message_to{message="hello", to_name=fred)

    This creates:

    {message_to, fred, "hello"}

    Notice that you do not have to worry about the order you assign values to the │ │ │ +

    A record is defined as:

    -record(name_of_record,{field_name1, field_name2, field_name3, ......}).

    For example:

    -record(message_to,{to_name, message}).

    This is equivalent to:

    {message_to, To_Name, Message}

    Creating a record is best illustrated by an example:

    #message_to{message="hello", to_name=fred)

    This creates:

    {message_to, fred, "hello"}

    Notice that you do not have to worry about the order you assign values to the │ │ │ various parts of the records when you create it. The advantage of using records │ │ │ is that by placing their definitions in header files you can conveniently define │ │ │ interfaces that are easy to change. For example, if you want to add a new field │ │ │ to the record, you only have to change the code where the new field is used and │ │ │ not at every place the record is referred to. If you leave out a field when │ │ │ creating a record, it gets the value of the atom undefined. (manual)

    Pattern matching with records is very similar to creating records. For example, │ │ │ -inside a case or receive:

    #message_to{to_name=ToName, message=Message} ->

    This is the same as:

    {message_to, ToName, Message}

    │ │ │ +inside a case or receive:

    #message_to{to_name=ToName, message=Message} ->

    This is the same as:

    {message_to, ToName, Message}

    │ │ │ │ │ │ │ │ │ │ │ │ Macros │ │ │

    │ │ │

    Another thing that has been added to the messenger is a macro. The file │ │ │ mess_config.hrl contains the definition:

    %%% Configure the location of the server node,
    │ │ │ --define(server_node, messenger@super).

    This file is included in mess_server.erl:

    -include("mess_config.hrl").

    Every occurrence of ?server_node in mess_server.erl is now replaced by │ │ │ -messenger@super.

    A macro is also used when spawning the server process:

    spawn(?MODULE, server, [])

    This is a standard macro (that is, defined by the system, not by the user). │ │ │ +-define(server_node, messenger@super).

    This file is included in mess_server.erl:

    -include("mess_config.hrl").

    Every occurrence of ?server_node in mess_server.erl is now replaced by │ │ │ +messenger@super.

    A macro is also used when spawning the server process:

    spawn(?MODULE, server, [])

    This is a standard macro (that is, defined by the system, not by the user). │ │ │ ?MODULE is always replaced by the name of the current module (that is, the │ │ │ -module definition near the start of the file). There are more advanced ways │ │ │ of using macros with, for example, parameters.

    The three Erlang (.erl) files in the messenger example are individually │ │ │ compiled into object code file (.beam). The Erlang system loads and links │ │ │ these files into the system when they are referred to during execution of the │ │ │ code. In this case, they are simply put in our current working directory (that │ │ │ is, the place you have done "cd" to). There are ways of putting the .beam │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/ref_man_functions.html │ │ │ @@ -120,51 +120,51 @@ │ │ │ │ │ │ │ │ │ Function Declaration Syntax │ │ │ │ │ │

    A function declaration is a sequence of function clauses separated by │ │ │ semicolons, and terminated by a period (.).

    A function clause consists of a clause head and a clause body, separated by │ │ │ ->.

    A clause head consists of the function name, an argument list, and an optional │ │ │ -guard sequence beginning with the keyword when:

    Name(Pattern11,...,Pattern1N) [when GuardSeq1] ->
    │ │ │ +guard sequence beginning with the keyword when:

    Name(Pattern11,...,Pattern1N) [when GuardSeq1] ->
    │ │ │      Body1;
    │ │ │  ...;
    │ │ │ -Name(PatternK1,...,PatternKN) [when GuardSeqK] ->
    │ │ │ +Name(PatternK1,...,PatternKN) [when GuardSeqK] ->
    │ │ │      BodyK.

    The function name is an atom. Each argument is a pattern.

    The number of arguments N is the arity of the function. A function is │ │ │ uniquely defined by the module name, function name, and arity. That is, two │ │ │ functions with the same name and in the same module, but with different arities │ │ │ are two different functions.

    A function named f in module mod and with arity N is often denoted as │ │ │ mod:f/N.

    A clause body consists of a sequence of expressions separated by comma (,):

    Expr1,
    │ │ │  ...,
    │ │ │  ExprN

    Valid Erlang expressions and guard sequences are described in │ │ │ -Expressions.

    Example:

    fact(N) when N > 0 ->  % first clause head
    │ │ │ -    N * fact(N-1);     % first clause body
    │ │ │ +Expressions.

    Example:

    fact(N) when N > 0 ->  % first clause head
    │ │ │ +    N * fact(N-1);     % first clause body
    │ │ │  
    │ │ │ -fact(0) ->             % second clause head
    │ │ │ +fact(0) ->             % second clause head
    │ │ │      1.                 % second clause body

    │ │ │ │ │ │ │ │ │ │ │ │ Function Evaluation │ │ │

    │ │ │

    When a function M:F/N is called, first the code for the function is located. │ │ │ If the function cannot be found, an undef runtime error occurs. Notice that │ │ │ the function must be exported to be visible outside the module it is defined in.

    If the function is found, the function clauses are scanned sequentially until a │ │ │ clause is found that fulfills both of the following two conditions:

    1. The patterns in the clause head can be successfully matched against the given │ │ │ arguments.
    2. The guard sequence, if any, is true.

    If such a clause cannot be found, a function_clause runtime error occurs.

    If such a clause is found, the corresponding clause body is evaluated. That is, │ │ │ the expressions in the body are evaluated sequentially and the value of the last │ │ │ -expression is returned.

    Consider the function fact:

    -module(mod).
    │ │ │ --export([fact/1]).
    │ │ │ +expression is returned.

    Consider the function fact:

    -module(mod).
    │ │ │ +-export([fact/1]).
    │ │ │  
    │ │ │ -fact(N) when N > 0 ->
    │ │ │ -    N * fact(N - 1);
    │ │ │ -fact(0) ->
    │ │ │ +fact(N) when N > 0 ->
    │ │ │ +    N * fact(N - 1);
    │ │ │ +fact(0) ->
    │ │ │      1.

    Assume that you want to calculate the factorial for 1:

    1> mod:fact(1).

    Evaluation starts at the first clause. The pattern N is matched against │ │ │ argument 1. The matching succeeds and the guard (N > 0) is true, thus N is │ │ │ -bound to 1, and the corresponding body is evaluated:

    N * fact(N-1) => (N is bound to 1)
    │ │ │ -1 * fact(0)

    Now, fact(0) is called, and the function clauses are scanned │ │ │ +bound to 1, and the corresponding body is evaluated:

    N * fact(N-1) => (N is bound to 1)
    │ │ │ +1 * fact(0)

    Now, fact(0) is called, and the function clauses are scanned │ │ │ sequentially again. First, the pattern N is matched against 0. The │ │ │ matching succeeds, but the guard (N > 0) is false. Second, the │ │ │ pattern 0 is matched against the argument 0. The matching succeeds │ │ │ and the body is evaluated:

    1 * fact(0) =>
    │ │ │  1 * 1 =>
    │ │ │  1

    Evaluation has succeed and mod:fact(1) returns 1.

    If mod:fact/1 is called with a negative number as argument, no clause head │ │ │ matches. A function_clause runtime error occurs.

    │ │ │ @@ -173,17 +173,17 @@ │ │ │ │ │ │ Tail recursion │ │ │

    │ │ │

    If the last expression of a function body is a function call, a │ │ │ tail-recursive call is done. This is to ensure that no system │ │ │ resources, for example, call stack, are consumed. This means that an │ │ │ infinite loop using tail-recursive calls will not exhaust the call │ │ │ -stack and can (in principle) run forever.

    Example:

    loop(N) ->
    │ │ │ -    io:format("~w~n", [N]),
    │ │ │ -    loop(N+1).

    The earlier factorial example is a counter-example. It is not │ │ │ +stack and can (in principle) run forever.

    Example:

    loop(N) ->
    │ │ │ +    io:format("~w~n", [N]),
    │ │ │ +    loop(N+1).

    The earlier factorial example is a counter-example. It is not │ │ │ tail-recursive, since a multiplication is done on the result of the recursive │ │ │ call to fact(N-1).

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

    │ │ │ @@ -191,17 +191,17 @@ │ │ │ system. BIFs do things that are difficult or impossible to implement │ │ │ in Erlang. Most of the BIFs belong to module erlang, but there │ │ │ are also BIFs belonging to a few other modules, for example lists │ │ │ and ets.

    The most commonly used BIFs belonging to erlang are auto-imported. They do │ │ │ not need to be prefixed with the module name. Which BIFs that are auto-imported │ │ │ is specified in the erlang module in ERTS. For example, standard-type │ │ │ conversion BIFs like atom_to_list and BIFs allowed in guards can be called │ │ │ -without specifying the module name.

    Examples:

    1> tuple_size({a,b,c}).
    │ │ │ +without specifying the module name.

    Examples:

    1> tuple_size({a,b,c}).
    │ │ │  3
    │ │ │ -2> atom_to_list('Erlang').
    │ │ │ +2> atom_to_list('Erlang').
    │ │ │  "Erlang"
    │ │ │ │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │

    │ │ │ │ │ │ │ │ │ Process Creation │ │ │

    │ │ │ -

    A process is created by calling spawn():

    spawn(Module, Name, Args) -> pid()
    │ │ │ -  Module = Name = atom()
    │ │ │ -  Args = [Arg1,...,ArgN]
    │ │ │ -    ArgI = term()

    spawn() creates a new process and returns the pid.

    The new process starts executing in Module:Name(Arg1,...,ArgN) where the │ │ │ +

    A process is created by calling spawn():

    spawn(Module, Name, Args) -> pid()
    │ │ │ +  Module = Name = atom()
    │ │ │ +  Args = [Arg1,...,ArgN]
    │ │ │ +    ArgI = term()

    spawn() creates a new process and returns the pid.

    The new process starts executing in Module:Name(Arg1,...,ArgN) where the │ │ │ arguments are the elements of the (possible empty) Args argument list.

    There exist a number of different spawn BIFs:

    │ │ │ │ │ │ │ │ │ │ │ │ Registered Processes │ │ │

    │ │ │

    Besides addressing a process by using its pid, there are also BIFs for │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/ref_man_records.html │ │ │ @@ -123,17 +123,17 @@ │ │ │ │ │ │ │ │ │ Defining Records │ │ │ │ │ │

    A record definition consists of the name of the record, followed by the field │ │ │ names of the record. Record and field names must be atoms. Each field can be │ │ │ given an optional default value. If no default value is supplied, undefined is │ │ │ -used.

    -record(Name, {Field1 [= Expr1],
    │ │ │ +used.

    -record(Name, {Field1 [= Expr1],
    │ │ │                 ...
    │ │ │ -               FieldN [= ExprN]}).

    The default value for a field is an arbitrary expression, except that it must │ │ │ + FieldN [= ExprN]}).

    The default value for a field is an arbitrary expression, except that it must │ │ │ not use any variables.

    A record definition can be placed anywhere among the attributes and function │ │ │ declarations of a module, but the definition must come before any usage of the │ │ │ record.

    If a record is used in several modules, it is recommended that the record │ │ │ definition is placed in an include file.

    Change

    Starting from Erlang/OTP 26, records can be defined in the Erlang shell │ │ │ using the syntax described in this section. In earlier releases, it was │ │ │ necessary to use the shell built-in function rd/2.

    │ │ │ │ │ │ @@ -143,32 +143,32 @@ │ │ │

    │ │ │

    The following expression creates a new Name record where the value of each │ │ │ field FieldI is the value of evaluating the corresponding expression ExprI:

    #Name{Field1=Expr1, ..., FieldK=ExprK}

    The fields can be in any order, not necessarily the same order as in the record │ │ │ definition, and fields can be omitted. Omitted fields get their respective │ │ │ default value instead.

    If several fields are to be assigned the same value, the following construction │ │ │ can be used:

    #Name{Field1=Expr1, ..., FieldK=ExprK, _=ExprL}

    Omitted fields then get the value of evaluating ExprL instead of their default │ │ │ values. This feature is primarily intended to be used to create patterns for ETS │ │ │ -and Mnesia match functions.

    Example:

    -record(person, {name, phone, address}).
    │ │ │ +and Mnesia match functions.

    Example:

    -record(person, {name, phone, address}).
    │ │ │  
    │ │ │ -lookup(Name, Tab) ->
    │ │ │ -    ets:match_object(Tab, #person{name=Name, _='_'}).

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

    │ │ │ │ │ │ │ │ │ │ │ │ Accessing Record Fields │ │ │

    │ │ │
    Expr#Name.Field

    Returns the value of the specified field. Expr is to evaluate to a Name │ │ │ -record.

    Example:

    -record(person, {name, phone, address}).
    │ │ │ +record.

    Example:

    -record(person, {name, phone, address}).
    │ │ │  
    │ │ │ -get_person_name(Person) ->
    │ │ │ +get_person_name(Person) ->
    │ │ │      Person#person.name.

    The following expression returns the position of the specified field in the │ │ │ -tuple representation of the record:

    #Name.Field

    Example:

    -record(person, {name, phone, address}).
    │ │ │ +tuple representation of the record:

    #Name.Field

    Example:

    -record(person, {name, phone, address}).
    │ │ │  
    │ │ │ -lookup(Name, List) ->
    │ │ │ -    lists:keyfind(Name, #person.name, List).

    │ │ │ +lookup(Name, List) -> │ │ │ + lists:keyfind(Name, #person.name, List).

    │ │ │ │ │ │ │ │ │ │ │ │ Updating Records │ │ │

    │ │ │
    Expr#Name{Field1=Expr1, ..., FieldK=ExprK}

    Expr is to evaluate to a Name record. A copy of this record is returned, │ │ │ with the value of each specified field FieldI changed to the value of │ │ │ @@ -178,51 +178,51 @@ │ │ │ │ │ │ │ │ │ Records in Guards │ │ │ │ │ │

    Since record expressions are expanded to tuple expressions, creating │ │ │ records and accessing record fields are allowed in guards. However, │ │ │ all subexpressions (for initializing fields), must be valid guard │ │ │ -expressions as well.

    Examples:

    handle(Msg, State) when Msg =:= #msg{to=void, no=3} ->
    │ │ │ +expressions as well.

    Examples:

    handle(Msg, State) when Msg =:= #msg{to=void, no=3} ->
    │ │ │      ...
    │ │ │  
    │ │ │ -handle(Msg, State) when State#state.running =:= true ->
    │ │ │ -    ...

    There is also a type test BIF is_record(Term, RecordTag).

    Example:

    is_person(P) when is_record(P, person) ->
    │ │ │ +handle(Msg, State) when State#state.running =:= true ->
    │ │ │ +    ...

    There is also a type test BIF is_record(Term, RecordTag).

    Example:

    is_person(P) when is_record(P, person) ->
    │ │ │      true;
    │ │ │ -is_person(_P) ->
    │ │ │ +is_person(_P) ->
    │ │ │      false.

    │ │ │ │ │ │ │ │ │ │ │ │ Records in Patterns │ │ │

    │ │ │

    A pattern that matches a certain record is created in the same way as a record │ │ │ is created:

    #Name{Field1=Expr1, ..., FieldK=ExprK}

    In this case, one or more of Expr1 ... ExprK can be unbound variables.

    │ │ │ │ │ │ │ │ │ │ │ │ Nested Records │ │ │

    │ │ │ -

    Assume the following record definitions:

    -record(nrec0, {name = "nested0"}).
    │ │ │ --record(nrec1, {name = "nested1", nrec0=#nrec0{}}).
    │ │ │ --record(nrec2, {name = "nested2", nrec1=#nrec1{}}).
    │ │ │ +

    Assume the following record definitions:

    -record(nrec0, {name = "nested0"}).
    │ │ │ +-record(nrec1, {name = "nested1", nrec0=#nrec0{}}).
    │ │ │ +-record(nrec2, {name = "nested2", nrec1=#nrec1{}}).
    │ │ │  
    │ │ │ -N2 = #nrec2{},

    Accessing or updating nested records can be written without parentheses:

    "nested0" = N2#nrec2.nrec1#nrec1.nrec0#nrec0.name,
    │ │ │ +N2 = #nrec2{},

    Accessing or updating nested records can be written without parentheses:

    "nested0" = N2#nrec2.nrec1#nrec1.nrec0#nrec0.name,
    │ │ │      N0n = N2#nrec2.nrec1#nrec1.nrec0#nrec0{name = "nested0a"},

    which is equivalent to:

    "nested0" = ((N2#nrec2.nrec1)#nrec1.nrec0)#nrec0.name,
    │ │ │  N0n = ((N2#nrec2.nrec1)#nrec1.nrec0)#nrec0{name = "nested0a"},

    Change

    Before Erlang/OTP R14, parentheses were necessary when accessing or updating │ │ │ nested records.

    │ │ │ │ │ │ │ │ │ │ │ │ Internal Representation of Records │ │ │

    │ │ │

    Record expressions are translated to tuple expressions during compilation. A │ │ │ -record defined as:

    -record(Name, {Field1, ..., FieldN}).

    is internally represented by the tuple:

    {Name, Value1, ..., ValueN}

    Here each ValueI is the default value for FieldI.

    To each module using records, a pseudo function is added during compilation to │ │ │ -obtain information about records:

    record_info(fields, Record) -> [Field]
    │ │ │ -record_info(size, Record) -> Size

    Size is the size of the tuple representation, that is, one more than the │ │ │ +record defined as:

    -record(Name, {Field1, ..., FieldN}).

    is internally represented by the tuple:

    {Name, Value1, ..., ValueN}

    Here each ValueI is the default value for FieldI.

    To each module using records, a pseudo function is added during compilation to │ │ │ +obtain information about records:

    record_info(fields, Record) -> [Field]
    │ │ │ +record_info(size, Record) -> Size

    Size is the size of the tuple representation, that is, one more than the │ │ │ number of fields.

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

    gen_server, simple code replacement is not sufficient. │ │ │ Instead, it is necessary to:

    • Suspend the processes using the module (to avoid that they try to handle any │ │ │ requests before the code replacement is completed).
    • Ask them to transform the internal state format and switch to the new version │ │ │ of the module.
    • Remove the old version.
    • Resume the processes.

    This is called synchronized code replacement and for this the following │ │ │ -instructions are used:

    {update, Module, {advanced, Extra}}
    │ │ │ -{update, Module, supervisor}

    update with argument {advanced,Extra} is used when changing the internal │ │ │ +instructions are used:

    {update, Module, {advanced, Extra}}
    │ │ │ +{update, Module, supervisor}

    update with argument {advanced,Extra} is used when changing the internal │ │ │ state of a behaviour as described above. It causes behaviour processes to call │ │ │ the callback function code_change/3, passing the term Extra and some other │ │ │ information as arguments. See the manual pages for the respective behaviours and │ │ │ Appup Cookbook.

    update with argument supervisor is used when changing the start │ │ │ specification of a supervisor. See Appup Cookbook.

    When a module is to be updated, the release handler finds which processes that │ │ │ are using the module by traversing the supervision tree of each running │ │ │ -application and checking all the child specifications:

    {Id, StartFunc, Restart, Shutdown, Type, Modules}

    A process uses a module if the name is listed in Modules in the child │ │ │ +application and checking all the child specifications:

    {Id, StartFunc, Restart, Shutdown, Type, Modules}

    A process uses a module if the name is listed in Modules in the child │ │ │ specification for the process.

    If Modules=dynamic, which is the case for event managers, the event manager │ │ │ process informs the release handler about the list of currently installed event │ │ │ handlers (gen_event), and it is checked if the module name is in this list │ │ │ instead.

    The release handler suspends, asks for code change, and resumes processes by │ │ │ calling the functions sys:suspend/1,2, sys:change_code/4,5, and │ │ │ sys:resume/1,2, respectively.

    │ │ │ │ │ │ │ │ │ │ │ │ add_module and delete_module │ │ │

    │ │ │ -

    If a new module is introduced, the following instruction is used:

    {add_module, Module}

    This instruction loads module Module. When running Erlang in │ │ │ +

    If a new module is introduced, the following instruction is used:

    {add_module, Module}

    This instruction loads module Module. When running Erlang in │ │ │ embedded mode it is necessary to use this this instruction. It is not │ │ │ strictly required when running Erlang in interactive mode, since the │ │ │ -code server automatically searches for and loads unloaded modules.

    The opposite of add_module is delete_module, which unloads a module:

    {delete_module, Module}

    Any process, in any application, with Module as residence module, is │ │ │ +code server automatically searches for and loads unloaded modules.

    The opposite of add_module is delete_module, which unloads a module:

    {delete_module, Module}

    Any process, in any application, with Module as residence module, is │ │ │ killed when the instruction is evaluated. Therefore, the user must │ │ │ ensure that all such processes are terminated before deleting module │ │ │ Module to avoid a situation with failing supervisor restarts.

    │ │ │ │ │ │ │ │ │ │ │ │ Application Instructions │ │ │ @@ -327,74 +327,74 @@ │ │ │ │ │ │ │ │ │ Application Upgrade File │ │ │

    │ │ │

    To define how to upgrade/downgrade between the current version and previous │ │ │ versions of an application, an application upgrade file, or in short │ │ │ .appup file is created. The file is to be called Application.appup, where │ │ │ -Application is the application name:

    {Vsn,
    │ │ │ - [{UpFromVsn1, InstructionsU1},
    │ │ │ +Application is the application name:

    {Vsn,
    │ │ │ + [{UpFromVsn1, InstructionsU1},
    │ │ │    ...,
    │ │ │ -  {UpFromVsnK, InstructionsUK}],
    │ │ │ - [{DownToVsn1, InstructionsD1},
    │ │ │ +  {UpFromVsnK, InstructionsUK}],
    │ │ │ + [{DownToVsn1, InstructionsD1},
    │ │ │    ...,
    │ │ │ -  {DownToVsnK, InstructionsDK}]}.
    • Vsn, a string, is the current version of the application, as defined in the │ │ │ + {DownToVsnK, InstructionsDK}]}.
    • Vsn, a string, is the current version of the application, as defined in the │ │ │ .app file.
    • Each UpFromVsn is a previous version of the application to upgrade from.
    • Each DownToVsn is a previous version of the application to downgrade to.
    • Each Instructions is a list of release handling instructions.

    UpFromVsn and DownToVsn can also be specified as regular expressions. For │ │ │ more information about the syntax and contents of the .appup file, see │ │ │ appup in SASL.

    Appup Cookbook includes examples of .appup files for │ │ │ typical upgrade/downgrade cases.

    Example: Consider the release ch_rel-1 from │ │ │ Releases. Assume you want to add a function │ │ │ available/0 to server ch3, which returns the number of available channels │ │ │ (when trying out the example, make the change in a copy of the original │ │ │ -directory, to ensure that the first version is still available):

    -module(ch3).
    │ │ │ --behaviour(gen_server).
    │ │ │ +directory, to ensure that the first version is still available):

    -module(ch3).
    │ │ │ +-behaviour(gen_server).
    │ │ │  
    │ │ │ --export([start_link/0]).
    │ │ │ --export([alloc/0, free/1]).
    │ │ │ --export([available/0]).
    │ │ │ --export([init/1, handle_call/3, handle_cast/2]).
    │ │ │ +-export([start_link/0]).
    │ │ │ +-export([alloc/0, free/1]).
    │ │ │ +-export([available/0]).
    │ │ │ +-export([init/1, handle_call/3, handle_cast/2]).
    │ │ │  
    │ │ │ -start_link() ->
    │ │ │ -    gen_server:start_link({local, ch3}, ch3, [], []).
    │ │ │ +start_link() ->
    │ │ │ +    gen_server:start_link({local, ch3}, ch3, [], []).
    │ │ │  
    │ │ │ -alloc() ->
    │ │ │ -    gen_server:call(ch3, alloc).
    │ │ │ +alloc() ->
    │ │ │ +    gen_server:call(ch3, alloc).
    │ │ │  
    │ │ │ -free(Ch) ->
    │ │ │ -    gen_server:cast(ch3, {free, Ch}).
    │ │ │ +free(Ch) ->
    │ │ │ +    gen_server:cast(ch3, {free, Ch}).
    │ │ │  
    │ │ │ -available() ->
    │ │ │ -    gen_server:call(ch3, available).
    │ │ │ +available() ->
    │ │ │ +    gen_server:call(ch3, available).
    │ │ │  
    │ │ │ -init(_Args) ->
    │ │ │ -    {ok, channels()}.
    │ │ │ +init(_Args) ->
    │ │ │ +    {ok, channels()}.
    │ │ │  
    │ │ │ -handle_call(alloc, _From, Chs) ->
    │ │ │ -    {Ch, Chs2} = alloc(Chs),
    │ │ │ -    {reply, Ch, Chs2};
    │ │ │ -handle_call(available, _From, Chs) ->
    │ │ │ -    N = available(Chs),
    │ │ │ -    {reply, N, Chs}.
    │ │ │ +handle_call(alloc, _From, Chs) ->
    │ │ │ +    {Ch, Chs2} = alloc(Chs),
    │ │ │ +    {reply, Ch, Chs2};
    │ │ │ +handle_call(available, _From, Chs) ->
    │ │ │ +    N = available(Chs),
    │ │ │ +    {reply, N, Chs}.
    │ │ │  
    │ │ │ -handle_cast({free, Ch}, Chs) ->
    │ │ │ -    Chs2 = free(Ch, Chs),
    │ │ │ -    {noreply, Chs2}.

    A new version of the ch_app.app file must now be created, where the version is │ │ │ -updated:

    {application, ch_app,
    │ │ │ - [{description, "Channel allocator"},
    │ │ │ -  {vsn, "2"},
    │ │ │ -  {modules, [ch_app, ch_sup, ch3]},
    │ │ │ -  {registered, [ch3]},
    │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ -  {mod, {ch_app,[]}}
    │ │ │ - ]}.

    To upgrade ch_app from "1" to "2" (and to downgrade from "2" to "1"), │ │ │ +handle_cast({free, Ch}, Chs) -> │ │ │ + Chs2 = free(Ch, Chs), │ │ │ + {noreply, Chs2}.

    A new version of the ch_app.app file must now be created, where the version is │ │ │ +updated:

    {application, ch_app,
    │ │ │ + [{description, "Channel allocator"},
    │ │ │ +  {vsn, "2"},
    │ │ │ +  {modules, [ch_app, ch_sup, ch3]},
    │ │ │ +  {registered, [ch3]},
    │ │ │ +  {applications, [kernel, stdlib, sasl]},
    │ │ │ +  {mod, {ch_app,[]}}
    │ │ │ + ]}.

    To upgrade ch_app from "1" to "2" (and to downgrade from "2" to "1"), │ │ │ you only need to load the new (old) version of the ch3 callback module. Create │ │ │ -the application upgrade file ch_app.appup in the ebin directory:

    {"2",
    │ │ │ - [{"1", [{load_module, ch3}]}],
    │ │ │ - [{"1", [{load_module, ch3}]}]
    │ │ │ -}.

    │ │ │ +the application upgrade file ch_app.appup in the ebin directory:

    {"2",
    │ │ │ + [{"1", [{load_module, ch3}]}],
    │ │ │ + [{"1", [{load_module, ch3}]}]
    │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ Release Upgrade File │ │ │

    │ │ │

    To define how to upgrade/downgrade between the new version and previous versions │ │ │ of a release, a release upgrade file, or in short .relup file, is to be │ │ │ @@ -405,22 +405,22 @@ │ │ │ are to be added and deleted, and which applications that must be upgraded and/or │ │ │ downgraded. The instructions for this are fetched from the .appup files and │ │ │ transformed into a single list of low-level instructions in the right order.

    If the relup file is relatively simple, it can be created manually. It is only │ │ │ to contain low-level instructions.

    For details about the syntax and contents of the release upgrade file, see │ │ │ relup in SASL.

    Example, continued from the previous section: You have a new version "2" of │ │ │ ch_app and an .appup file. A new version of the .rel file is also needed. │ │ │ This time the file is called ch_rel-2.rel and the release version string is │ │ │ -changed from "A" to "B":

    {release,
    │ │ │ - {"ch_rel", "B"},
    │ │ │ - {erts, "14.2.5"},
    │ │ │ - [{kernel, "9.2.4"},
    │ │ │ -  {stdlib, "5.2.3"},
    │ │ │ -  {sasl, "4.2.1"},
    │ │ │ -  {ch_app, "2"}]
    │ │ │ -}.

    Now the relup file can be generated:

    1> systools:make_relup("ch_rel-2", ["ch_rel-1"], ["ch_rel-1"]).
    │ │ │ +changed from "A" to "B":

    {release,
    │ │ │ + {"ch_rel", "B"},
    │ │ │ + {erts, "14.2.5"},
    │ │ │ + [{kernel, "9.2.4"},
    │ │ │ +  {stdlib, "5.2.3"},
    │ │ │ +  {sasl, "4.2.1"},
    │ │ │ +  {ch_app, "2"}]
    │ │ │ +}.

    Now the relup file can be generated:

    1> systools:make_relup("ch_rel-2", ["ch_rel-1"], ["ch_rel-1"]).
    │ │ │  ok

    This generates a relup file with instructions for how to upgrade from version │ │ │ "A" ("ch_rel-1") to version "B" ("ch_rel-2") and how to downgrade from version │ │ │ "B" to version "A".

    Both the old and new versions of the .app and .rel files must be in the code │ │ │ path, as well as the .appup and (new) .beam files. The code path can be │ │ │ extended by using the option path:

    1> systools:make_relup("ch_rel-2", ["ch_rel-1"], ["ch_rel-1"],
    │ │ │  [{path,["../ch_rel-1",
    │ │ │  "../ch_rel-1/lib/ch_app-1/ebin"]}]).
    │ │ │ @@ -433,25 +433,25 @@
    │ │ │  

    When you have made a new version of a release, a release package can be created │ │ │ with this new version and transferred to the target environment.

    To install the new version of the release in runtime, the release │ │ │ handler is used. This is a process belonging to the SASL application, │ │ │ which handles unpacking, installation, and removal of release │ │ │ packages. The release_handler module communicates with this process.

    Assuming there is an operational target system with installation root directory │ │ │ $ROOT, the release package with the new version of the release is to be copied │ │ │ to $ROOT/releases.

    First, unpack the release package. The files are then extracted from the │ │ │ -package:

    release_handler:unpack_release(ReleaseName) => {ok, Vsn}
    • ReleaseName is the name of the release package except the .tar.gz │ │ │ +package:

      release_handler:unpack_release(ReleaseName) => {ok, Vsn}
      • ReleaseName is the name of the release package except the .tar.gz │ │ │ extension.
      • Vsn is the version of the unpacked release, as defined in its .rel file.

      A directory $ROOT/lib/releases/Vsn is created, where the .rel file, the boot │ │ │ script start.boot, the system configuration file sys.config, and relup are │ │ │ placed. For applications with new version numbers, the application directories │ │ │ are placed under $ROOT/lib. Unchanged applications are not affected.

      An unpacked release can be installed. The release handler then evaluates the │ │ │ -instructions in relup, step by step:

      release_handler:install_release(Vsn) => {ok, FromVsn, []}

      If an error occurs during the installation, the system is rebooted using the old │ │ │ +instructions in relup, step by step:

      release_handler:install_release(Vsn) => {ok, FromVsn, []}

      If an error occurs during the installation, the system is rebooted using the old │ │ │ version of the release. If installation succeeds, the system is afterwards using │ │ │ the new version of the release, but if anything happens and the system is │ │ │ rebooted, it starts using the previous version again.

      To be made the default version, the newly installed release must be made │ │ │ permanent, which means the previous version becomes old:

      release_handler:make_permanent(Vsn) => ok

      The system keeps information about which versions are old and permanent in the │ │ │ -files $ROOT/releases/RELEASES and $ROOT/releases/start_erl.data.

      To downgrade from Vsn to FromVsn, install_release must be called again:

      release_handler:install_release(FromVsn) => {ok, Vsn, []}

      An installed, but not permanent, release can be removed. Information about the │ │ │ +files $ROOT/releases/RELEASES and $ROOT/releases/start_erl.data.

      To downgrade from Vsn to FromVsn, install_release must be called again:

      release_handler:install_release(FromVsn) => {ok, Vsn, []}

      An installed, but not permanent, release can be removed. Information about the │ │ │ release is then deleted from $ROOT/releases/RELEASES and the release-specific │ │ │ code, that is, the new application directories and the $ROOT/releases/Vsn │ │ │ directory, are removed.

      release_handler:remove_release(Vsn) => ok

      │ │ │ │ │ │ │ │ │ │ │ │ Example (continued from the previous sections) │ │ │ @@ -462,17 +462,17 @@ │ │ │ is needed, the file is to contain the empty list:

      [].

      Step 2) Start the system as a simple target system. In reality, it is to be │ │ │ started as an embedded system. However, using erl with the correct boot script │ │ │ and config file is enough for illustration purposes:

      % cd $ROOT
      │ │ │  % bin/erl -boot $ROOT/releases/A/start -config $ROOT/releases/A/sys
      │ │ │  ...

      $ROOT is the installation directory of the target system.

      Step 3) In another Erlang shell, generate start scripts and create a release │ │ │ package for the new version "B". Remember to include (a possible updated) │ │ │ sys.config and the relup file. For more information, see │ │ │ -Release Upgrade File.

      1> systools:make_script("ch_rel-2").
      │ │ │ +Release Upgrade File.

      1> systools:make_script("ch_rel-2").
      │ │ │  ok
      │ │ │ -2> systools:make_tar("ch_rel-2").
      │ │ │ +2> systools:make_tar("ch_rel-2").
      │ │ │  ok

      The new release package now also contains version "2" of ch_app and the │ │ │ relup file:

      % tar tf ch_rel-2.tar
      │ │ │  lib/kernel-9.2.4/ebin/kernel.app
      │ │ │  lib/kernel-9.2.4/ebin/application.beam
      │ │ │  ...
      │ │ │  lib/stdlib-5.2.3/ebin/stdlib.app
      │ │ │  lib/stdlib-5.2.3/ebin/argparse.beam
      │ │ │ @@ -485,31 +485,31 @@
      │ │ │  lib/ch_app-2/ebin/ch_sup.beam
      │ │ │  lib/ch_app-2/ebin/ch3.beam
      │ │ │  releases/B/start.boot
      │ │ │  releases/B/relup
      │ │ │  releases/B/sys.config
      │ │ │  releases/B/ch_rel-2.rel
      │ │ │  releases/ch_rel-2.rel

      Step 4) Copy the release package ch_rel-2.tar.gz to the $ROOT/releases │ │ │ -directory.

      Step 5) In the running target system, unpack the release package:

      1> release_handler:unpack_release("ch_rel-2").
      │ │ │ -{ok,"B"}

      The new application version ch_app-2 is installed under $ROOT/lib next to │ │ │ +directory.

      Step 5) In the running target system, unpack the release package:

      1> release_handler:unpack_release("ch_rel-2").
      │ │ │ +{ok,"B"}

      The new application version ch_app-2 is installed under $ROOT/lib next to │ │ │ ch_app-1. The kernel, stdlib, and sasl directories are not affected, as │ │ │ they have not changed.

      Under $ROOT/releases, a new directory B is created, containing │ │ │ -ch_rel-2.rel, start.boot, sys.config, and relup.

      Step 6) Check if the function ch3:available/0 is available:

      2> ch3:available().
      │ │ │ +ch_rel-2.rel, start.boot, sys.config, and relup.

      Step 6) Check if the function ch3:available/0 is available:

      2> ch3:available().
      │ │ │  ** exception error: undefined function ch3:available/0

      Step 7) Install the new release. The instructions in $ROOT/releases/B/relup │ │ │ are executed one by one, resulting in the new version of ch3 being loaded. The │ │ │ -function ch3:available/0 is now available:

      3> release_handler:install_release("B").
      │ │ │ -{ok,"A",[]}
      │ │ │ -4> ch3:available().
      │ │ │ +function ch3:available/0 is now available:

      3> release_handler:install_release("B").
      │ │ │ +{ok,"A",[]}
      │ │ │ +4> ch3:available().
      │ │ │  3
      │ │ │ -5> code:which(ch3).
      │ │ │ +5> code:which(ch3).
      │ │ │  ".../lib/ch_app-2/ebin/ch3.beam"
      │ │ │ -6> code:which(ch_sup).
      │ │ │ +6> code:which(ch_sup).
      │ │ │  ".../lib/ch_app-1/ebin/ch_sup.beam"

      Processes in ch_app for which code have not been updated, for example, the │ │ │ supervisor, are still evaluating code from ch_app-1.

      Step 8) If the target system is now rebooted, it uses version "A" again. The │ │ │ -"B" version must be made permanent, to be used when the system is rebooted.

      7> release_handler:make_permanent("B").
      │ │ │ +"B" version must be made permanent, to be used when the system is rebooted.

      7> release_handler:make_permanent("B").
      │ │ │  ok

      │ │ │ │ │ │ │ │ │ │ │ │ Updating Application Specifications │ │ │

      │ │ │

      When a new version of a release is installed, the application specifications are │ │ │ @@ -518,15 +518,15 @@ │ │ │ boot script is generated from the same .rel file as is used to build the │ │ │ release package itself.

      Specifically, the application configuration parameters are automatically updated │ │ │ according to (in increasing priority order):

      • The data in the boot script, fetched from the new application resource file │ │ │ App.app
      • The new sys.config
      • Command-line arguments -App Par Val

      This means that parameter values set in the other system configuration files and │ │ │ values set using application:set_env/3 are disregarded.

      When an installed release is made permanent, the system process init is set to │ │ │ point out the new sys.config.

      After the installation, the application controller compares the old and new │ │ │ configuration parameters for all running applications and call the callback │ │ │ -function:

      Module:config_change(Changed, New, Removed)
      • Module is the application callback module as defined by the mod key in the │ │ │ +function:

        Module:config_change(Changed, New, Removed)
        • Module is the application callback module as defined by the mod key in the │ │ │ .app file.
        • Changed and New are lists of {Par,Val} for all changed and added │ │ │ configuration parameters, respectively.
        • Removed is a list of all parameters Par that have been removed.

        The function is optional and can be omitted when implementing an application │ │ │ callback module.

        │ │ │

    │ │ │ │ │ │
    │ │ │
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/release_structure.html │ │ │ @@ -136,37 +136,37 @@ │ │ │ │ │ │ │ │ │ │ │ │ Release Resource File │ │ │ │ │ │

    To define a release, create a release resource file, or in short a .rel │ │ │ file. In the file, specify the name and version of the release, which ERTS │ │ │ -version it is based on, and which applications it consists of:

    {release, {Name,Vsn}, {erts, EVsn},
    │ │ │ - [{Application1, AppVsn1},
    │ │ │ +version it is based on, and which applications it consists of:

    {release, {Name,Vsn}, {erts, EVsn},
    │ │ │ + [{Application1, AppVsn1},
    │ │ │     ...
    │ │ │ -  {ApplicationN, AppVsnN}]}.

    Name, Vsn, EVsn, and AppVsn are strings.

    The file must be named Rel.rel, where Rel is a unique name.

    Each Application (atom) and AppVsn is the name and version of an application │ │ │ + {ApplicationN, AppVsnN}]}.

    Name, Vsn, EVsn, and AppVsn are strings.

    The file must be named Rel.rel, where Rel is a unique name.

    Each Application (atom) and AppVsn is the name and version of an application │ │ │ included in the release. The minimal release based on Erlang/OTP consists of the │ │ │ Kernel and STDLIB applications, so these applications must be included in the │ │ │ list.

    If the release is to be upgraded, it must also include the SASL application.

    Here is an example showing the .app file for a release of ch_app from │ │ │ -the Applications section:

    {application, ch_app,
    │ │ │ - [{description, "Channel allocator"},
    │ │ │ -  {vsn, "1"},
    │ │ │ -  {modules, [ch_app, ch_sup, ch3]},
    │ │ │ -  {registered, [ch3]},
    │ │ │ -  {applications, [kernel, stdlib, sasl]},
    │ │ │ -  {mod, {ch_app,[]}}
    │ │ │ - ]}.

    The .rel file must also contain kernel, stdlib, and sasl, as these │ │ │ -applications are required by ch_app. The file is called ch_rel-1.rel:

    {release,
    │ │ │ - {"ch_rel", "A"},
    │ │ │ - {erts, "14.2.5"},
    │ │ │ - [{kernel, "9.2.4"},
    │ │ │ -  {stdlib, "5.2.3"},
    │ │ │ -  {sasl, "4.2.1"},
    │ │ │ -  {ch_app, "1"}]
    │ │ │ -}.

    │ │ │ +the Applications section:

    {application, ch_app,
    │ │ │ + [{description, "Channel allocator"},
    │ │ │ +  {vsn, "1"},
    │ │ │ +  {modules, [ch_app, ch_sup, ch3]},
    │ │ │ +  {registered, [ch3]},
    │ │ │ +  {applications, [kernel, stdlib, sasl]},
    │ │ │ +  {mod, {ch_app,[]}}
    │ │ │ + ]}.

    The .rel file must also contain kernel, stdlib, and sasl, as these │ │ │ +applications are required by ch_app. The file is called ch_rel-1.rel:

    {release,
    │ │ │ + {"ch_rel", "A"},
    │ │ │ + {erts, "14.2.5"},
    │ │ │ + [{kernel, "9.2.4"},
    │ │ │ +  {stdlib, "5.2.3"},
    │ │ │ +  {sasl, "4.2.1"},
    │ │ │ +  {ch_app, "1"}]
    │ │ │ +}.

    │ │ │ │ │ │ │ │ │ │ │ │ Generating Boot Scripts │ │ │

    │ │ │

    systools in the SASL application includes tools to build and check │ │ │ releases. The functions read the .rel and .app files and perform │ │ │ @@ -190,17 +190,17 @@ │ │ │ │ │ │ │ │ │ │ │ │ Creating a Release Package │ │ │ │ │ │

    The systools:make_tar/1,2 function takes a │ │ │ .rel file as input and creates a zipped tar file with the code for │ │ │ -the specified applications, a release package:

    1> systools:make_script("ch_rel-1").
    │ │ │ +the specified applications, a release package:

    1> systools:make_script("ch_rel-1").
    │ │ │  ok
    │ │ │ -2> systools:make_tar("ch_rel-1").
    │ │ │ +2> systools:make_tar("ch_rel-1").
    │ │ │  ok

    The release package by default contains:

    • The .app files
    • The .rel file
    • The object code for all applications, structured according to the │ │ │ application directory structure
    • The binary boot script renamed to start.boot
    % tar tf ch_rel-1.tar
    │ │ │  lib/kernel-9.2.4/ebin/kernel.app
    │ │ │  lib/kernel-9.2.4/ebin/application.beam
    │ │ │  ...
    │ │ │  lib/stdlib-5.2.3/ebin/stdlib.app
    │ │ │  lib/stdlib-5.2.3/ebin/argparse.beam
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/robustness.html
    │ │ │ @@ -128,68 +128,68 @@
    │ │ │  
    │ │ │  

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

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

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

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

    After this is compiled and the file tut19.beam is copied to the necessary │ │ │ directories, the following is seen on (pong@kosken):

    (pong@kosken)1> tut19:start_pong().
    │ │ │  true
    │ │ │  Pong received ping
    │ │ │  Pong received ping
    │ │ │  Pong received ping
    │ │ │  Pong timed out

    And the following is seen on (ping@gollum):

    (ping@gollum)1> tut19:start_ping(pong@kosken).
    │ │ │  <0.36.0>
    │ │ │  Ping received pong
    │ │ │  Ping received pong
    │ │ │  Ping received pong
    │ │ │ -ping finished

    The time-out is set in:

    pong() ->
    │ │ │ +ping finished

    The time-out is set in:

    pong() ->
    │ │ │      receive
    │ │ │ -        {ping, Ping_PID} ->
    │ │ │ -            io:format("Pong received ping~n", []),
    │ │ │ +        {ping, Ping_PID} ->
    │ │ │ +            io:format("Pong received ping~n", []),
    │ │ │              Ping_PID ! pong,
    │ │ │ -            pong()
    │ │ │ +            pong()
    │ │ │      after 5000 ->
    │ │ │ -            io:format("Pong timed out~n", [])
    │ │ │ +            io:format("Pong timed out~n", [])
    │ │ │      end.

    The time-out (after 5000) is started when receive is entered. The time-out │ │ │ is canceled if {ping,Ping_PID} is received. If {ping,Ping_PID} is not │ │ │ received, the actions following the time-out are done after 5000 milliseconds. │ │ │ after must be last in the receive, that is, preceded by all other message │ │ │ reception specifications in the receive. It is also possible to call a │ │ │ -function that returned an integer for the time-out:

    after pong_timeout() ->

    In general, there are better ways than using time-outs to supervise parts of a │ │ │ +function that returned an integer for the time-out:

    after pong_timeout() ->

    In general, there are better ways than using time-outs to supervise parts of a │ │ │ distributed Erlang system. Time-outs are usually appropriate to supervise │ │ │ external events, for example, if you have expected a message from some external │ │ │ system within a specified time. For example, a time-out can be used to log a │ │ │ user out of the messenger system if they have not accessed it for, say, ten │ │ │ minutes.

    │ │ │ │ │ │ │ │ │ @@ -209,96 +209,96 @@ │ │ │ something called a signal to all the processes it has links to.

    The signal carries information about the pid it was sent from and the exit │ │ │ reason.

    The default behaviour of a process that receives a normal exit is to ignore the │ │ │ signal.

    The default behaviour in the two other cases (that is, abnormal exit) above is │ │ │ to:

    • Bypass all messages to the receiving process.
    • Kill the receiving process.
    • Propagate the same error signal to the links of the killed process.

    In this way you can connect all processes in a transaction together using links. │ │ │ If one of the processes exits abnormally, all the processes in the transaction │ │ │ are killed. As it is often wanted to create a process and link to it at the same │ │ │ time, there is a special BIF, spawn_link that does the │ │ │ -same as spawn, but also creates a link to the spawned process.

    Now an example of the ping pong example using links to terminate "pong":

    -module(tut20).
    │ │ │ +same as spawn, but also creates a link to the spawned process.

    Now an example of the ping pong example using links to terminate "pong":

    -module(tut20).
    │ │ │  
    │ │ │ --export([start/1,  ping/2, pong/0]).
    │ │ │ +-export([start/1,  ping/2, pong/0]).
    │ │ │  
    │ │ │ -ping(N, Pong_Pid) ->
    │ │ │ -    link(Pong_Pid),
    │ │ │ -    ping1(N, Pong_Pid).
    │ │ │ +ping(N, Pong_Pid) ->
    │ │ │ +    link(Pong_Pid),
    │ │ │ +    ping1(N, Pong_Pid).
    │ │ │  
    │ │ │ -ping1(0, _) ->
    │ │ │ -    exit(ping);
    │ │ │ +ping1(0, _) ->
    │ │ │ +    exit(ping);
    │ │ │  
    │ │ │ -ping1(N, Pong_Pid) ->
    │ │ │ -    Pong_Pid ! {ping, self()},
    │ │ │ +ping1(N, Pong_Pid) ->
    │ │ │ +    Pong_Pid ! {ping, self()},
    │ │ │      receive
    │ │ │          pong ->
    │ │ │ -            io:format("Ping received pong~n", [])
    │ │ │ +            io:format("Ping received pong~n", [])
    │ │ │      end,
    │ │ │ -    ping1(N - 1, Pong_Pid).
    │ │ │ +    ping1(N - 1, Pong_Pid).
    │ │ │  
    │ │ │ -pong() ->
    │ │ │ +pong() ->
    │ │ │      receive
    │ │ │ -        {ping, Ping_PID} ->
    │ │ │ -            io:format("Pong received ping~n", []),
    │ │ │ +        {ping, Ping_PID} ->
    │ │ │ +            io:format("Pong received ping~n", []),
    │ │ │              Ping_PID ! pong,
    │ │ │ -            pong()
    │ │ │ +            pong()
    │ │ │      end.
    │ │ │  
    │ │ │ -start(Ping_Node) ->
    │ │ │ -    PongPID = spawn(tut20, pong, []),
    │ │ │ -    spawn(Ping_Node, tut20, ping, [3, PongPID]).
    (s1@bill)3> tut20:start(s2@kosken).
    │ │ │ +start(Ping_Node) ->
    │ │ │ +    PongPID = spawn(tut20, pong, []),
    │ │ │ +    spawn(Ping_Node, tut20, ping, [3, PongPID]).
    (s1@bill)3> tut20:start(s2@kosken).
    │ │ │  Pong received ping
    │ │ │  <3820.41.0>
    │ │ │  Ping received pong
    │ │ │  Pong received ping
    │ │ │  Ping received pong
    │ │ │  Pong received ping
    │ │ │  Ping received pong

    This is a slight modification of the ping pong program where both processes are │ │ │ spawned from the same start/1 function, and the "ping" process can be spawned │ │ │ on a separate node. Notice the use of the link BIF. "Ping" calls │ │ │ exit(ping) when it finishes and this causes an exit signal to be │ │ │ sent to "pong", which also terminates.

    It is possible to modify the default behaviour of a process so that it does not │ │ │ get killed when it receives abnormal exit signals. Instead, all signals are │ │ │ turned into normal messages on the format {'EXIT',FromPID,Reason} and added to │ │ │ -the end of the receiving process' message queue. This behaviour is set by:

    process_flag(trap_exit, true)

    There are several other process flags, see erlang(3). │ │ │ +the end of the receiving process' message queue. This behaviour is set by:

    process_flag(trap_exit, true)

    There are several other process flags, see erlang(3). │ │ │ Changing the default behaviour of a process in this way is usually not done in │ │ │ standard user programs, but is left to the supervisory programs in OTP. However, │ │ │ -the ping pong program is modified to illustrate exit trapping.

    -module(tut21).
    │ │ │ +the ping pong program is modified to illustrate exit trapping.

    -module(tut21).
    │ │ │  
    │ │ │ --export([start/1,  ping/2, pong/0]).
    │ │ │ +-export([start/1,  ping/2, pong/0]).
    │ │ │  
    │ │ │ -ping(N, Pong_Pid) ->
    │ │ │ -    link(Pong_Pid),
    │ │ │ -    ping1(N, Pong_Pid).
    │ │ │ +ping(N, Pong_Pid) ->
    │ │ │ +    link(Pong_Pid),
    │ │ │ +    ping1(N, Pong_Pid).
    │ │ │  
    │ │ │ -ping1(0, _) ->
    │ │ │ -    exit(ping);
    │ │ │ +ping1(0, _) ->
    │ │ │ +    exit(ping);
    │ │ │  
    │ │ │ -ping1(N, Pong_Pid) ->
    │ │ │ -    Pong_Pid ! {ping, self()},
    │ │ │ +ping1(N, Pong_Pid) ->
    │ │ │ +    Pong_Pid ! {ping, self()},
    │ │ │      receive
    │ │ │          pong ->
    │ │ │ -            io:format("Ping received pong~n", [])
    │ │ │ +            io:format("Ping received pong~n", [])
    │ │ │      end,
    │ │ │ -    ping1(N - 1, Pong_Pid).
    │ │ │ +    ping1(N - 1, Pong_Pid).
    │ │ │  
    │ │ │ -pong() ->
    │ │ │ -    process_flag(trap_exit, true),
    │ │ │ -    pong1().
    │ │ │ +pong() ->
    │ │ │ +    process_flag(trap_exit, true),
    │ │ │ +    pong1().
    │ │ │  
    │ │ │ -pong1() ->
    │ │ │ +pong1() ->
    │ │ │      receive
    │ │ │ -        {ping, Ping_PID} ->
    │ │ │ -            io:format("Pong received ping~n", []),
    │ │ │ +        {ping, Ping_PID} ->
    │ │ │ +            io:format("Pong received ping~n", []),
    │ │ │              Ping_PID ! pong,
    │ │ │ -            pong1();
    │ │ │ -        {'EXIT', From, Reason} ->
    │ │ │ -            io:format("pong exiting, got ~p~n", [{'EXIT', From, Reason}])
    │ │ │ +            pong1();
    │ │ │ +        {'EXIT', From, Reason} ->
    │ │ │ +            io:format("pong exiting, got ~p~n", [{'EXIT', From, Reason}])
    │ │ │      end.
    │ │ │  
    │ │ │ -start(Ping_Node) ->
    │ │ │ -    PongPID = spawn(tut21, pong, []),
    │ │ │ -    spawn(Ping_Node, tut21, ping, [3, PongPID]).
    (s1@bill)1> tut21:start(s2@gollum).
    │ │ │ +start(Ping_Node) ->
    │ │ │ +    PongPID = spawn(tut21, pong, []),
    │ │ │ +    spawn(Ping_Node, tut21, ping, [3, PongPID]).
    (s1@bill)1> tut21:start(s2@gollum).
    │ │ │  <3820.39.0>
    │ │ │  Pong received ping
    │ │ │  Ping received pong
    │ │ │  Pong received ping
    │ │ │  Ping received pong
    │ │ │  Pong received ping
    │ │ │  Ping received pong
    │ │ │ @@ -351,135 +351,135 @@
    │ │ │  %%% Started: messenger:client(Server_Node, Name)
    │ │ │  %%% To client: logoff
    │ │ │  %%% To client: {message_to, ToName, Message}
    │ │ │  %%%
    │ │ │  %%% Configuration: change the server_node() function to return the
    │ │ │  %%% name of the node where the messenger server runs
    │ │ │  
    │ │ │ --module(messenger).
    │ │ │ --export([start_server/0, server/0,
    │ │ │ -         logon/1, logoff/0, message/2, client/2]).
    │ │ │ +-module(messenger).
    │ │ │ +-export([start_server/0, server/0,
    │ │ │ +         logon/1, logoff/0, message/2, client/2]).
    │ │ │  
    │ │ │  %%% Change the function below to return the name of the node where the
    │ │ │  %%% messenger server runs
    │ │ │ -server_node() ->
    │ │ │ +server_node() ->
    │ │ │      messenger@super.
    │ │ │  
    │ │ │  %%% This is the server process for the "messenger"
    │ │ │  %%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
    │ │ │ -server() ->
    │ │ │ -    process_flag(trap_exit, true),
    │ │ │ -    server([]).
    │ │ │ +server() ->
    │ │ │ +    process_flag(trap_exit, true),
    │ │ │ +    server([]).
    │ │ │  
    │ │ │ -server(User_List) ->
    │ │ │ +server(User_List) ->
    │ │ │      receive
    │ │ │ -        {From, logon, Name} ->
    │ │ │ -            New_User_List = server_logon(From, Name, User_List),
    │ │ │ -            server(New_User_List);
    │ │ │ -        {'EXIT', From, _} ->
    │ │ │ -            New_User_List = server_logoff(From, User_List),
    │ │ │ -            server(New_User_List);
    │ │ │ -        {From, message_to, To, Message} ->
    │ │ │ -            server_transfer(From, To, Message, User_List),
    │ │ │ -            io:format("list is now: ~p~n", [User_List]),
    │ │ │ -            server(User_List)
    │ │ │ +        {From, logon, Name} ->
    │ │ │ +            New_User_List = server_logon(From, Name, User_List),
    │ │ │ +            server(New_User_List);
    │ │ │ +        {'EXIT', From, _} ->
    │ │ │ +            New_User_List = server_logoff(From, User_List),
    │ │ │ +            server(New_User_List);
    │ │ │ +        {From, message_to, To, Message} ->
    │ │ │ +            server_transfer(From, To, Message, User_List),
    │ │ │ +            io:format("list is now: ~p~n", [User_List]),
    │ │ │ +            server(User_List)
    │ │ │      end.
    │ │ │  
    │ │ │  %%% Start the server
    │ │ │ -start_server() ->
    │ │ │ -    register(messenger, spawn(messenger, server, [])).
    │ │ │ +start_server() ->
    │ │ │ +    register(messenger, spawn(messenger, server, [])).
    │ │ │  
    │ │ │  %%% Server adds a new user to the user list
    │ │ │ -server_logon(From, Name, User_List) ->
    │ │ │ +server_logon(From, Name, User_List) ->
    │ │ │      %% check if logged on anywhere else
    │ │ │ -    case lists:keymember(Name, 2, User_List) of
    │ │ │ +    case lists:keymember(Name, 2, User_List) of
    │ │ │          true ->
    │ │ │ -            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
    │ │ │ +            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
    │ │ │              User_List;
    │ │ │          false ->
    │ │ │ -            From ! {messenger, logged_on},
    │ │ │ -            link(From),
    │ │ │ -            [{From, Name} | User_List]        %add user to the list
    │ │ │ +            From ! {messenger, logged_on},
    │ │ │ +            link(From),
    │ │ │ +            [{From, Name} | User_List]        %add user to the list
    │ │ │      end.
    │ │ │  
    │ │ │  %%% Server deletes a user from the user list
    │ │ │ -server_logoff(From, User_List) ->
    │ │ │ -    lists:keydelete(From, 1, User_List).
    │ │ │ +server_logoff(From, User_List) ->
    │ │ │ +    lists:keydelete(From, 1, User_List).
    │ │ │  
    │ │ │  
    │ │ │  %%% Server transfers a message between user
    │ │ │ -server_transfer(From, To, Message, User_List) ->
    │ │ │ +server_transfer(From, To, Message, User_List) ->
    │ │ │      %% check that the user is logged on and who he is
    │ │ │ -    case lists:keysearch(From, 1, User_List) of
    │ │ │ +    case lists:keysearch(From, 1, User_List) of
    │ │ │          false ->
    │ │ │ -            From ! {messenger, stop, you_are_not_logged_on};
    │ │ │ -        {value, {_, Name}} ->
    │ │ │ -            server_transfer(From, Name, To, Message, User_List)
    │ │ │ +            From ! {messenger, stop, you_are_not_logged_on};
    │ │ │ +        {value, {_, Name}} ->
    │ │ │ +            server_transfer(From, Name, To, Message, User_List)
    │ │ │      end.
    │ │ │  
    │ │ │  %%% If the user exists, send the message
    │ │ │ -server_transfer(From, Name, To, Message, User_List) ->
    │ │ │ +server_transfer(From, Name, To, Message, User_List) ->
    │ │ │      %% Find the receiver and send the message
    │ │ │ -    case lists:keysearch(To, 2, User_List) of
    │ │ │ +    case lists:keysearch(To, 2, User_List) of
    │ │ │          false ->
    │ │ │ -            From ! {messenger, receiver_not_found};
    │ │ │ -        {value, {ToPid, To}} ->
    │ │ │ -            ToPid ! {message_from, Name, Message},
    │ │ │ -            From ! {messenger, sent}
    │ │ │ +            From ! {messenger, receiver_not_found};
    │ │ │ +        {value, {ToPid, To}} ->
    │ │ │ +            ToPid ! {message_from, Name, Message},
    │ │ │ +            From ! {messenger, sent}
    │ │ │      end.
    │ │ │  
    │ │ │  %%% User Commands
    │ │ │ -logon(Name) ->
    │ │ │ -    case whereis(mess_client) of
    │ │ │ +logon(Name) ->
    │ │ │ +    case whereis(mess_client) of
    │ │ │          undefined ->
    │ │ │ -            register(mess_client,
    │ │ │ -                     spawn(messenger, client, [server_node(), Name]));
    │ │ │ +            register(mess_client,
    │ │ │ +                     spawn(messenger, client, [server_node(), Name]));
    │ │ │          _ -> already_logged_on
    │ │ │      end.
    │ │ │  
    │ │ │ -logoff() ->
    │ │ │ +logoff() ->
    │ │ │      mess_client ! logoff.
    │ │ │  
    │ │ │ -message(ToName, Message) ->
    │ │ │ -    case whereis(mess_client) of % Test if the client is running
    │ │ │ +message(ToName, Message) ->
    │ │ │ +    case whereis(mess_client) of % Test if the client is running
    │ │ │          undefined ->
    │ │ │              not_logged_on;
    │ │ │ -        _ -> mess_client ! {message_to, ToName, Message},
    │ │ │ +        _ -> mess_client ! {message_to, ToName, Message},
    │ │ │               ok
    │ │ │  end.
    │ │ │  
    │ │ │  %%% The client process which runs on each user node
    │ │ │ -client(Server_Node, Name) ->
    │ │ │ -    {messenger, Server_Node} ! {self(), logon, Name},
    │ │ │ -    await_result(),
    │ │ │ -    client(Server_Node).
    │ │ │ +client(Server_Node, Name) ->
    │ │ │ +    {messenger, Server_Node} ! {self(), logon, Name},
    │ │ │ +    await_result(),
    │ │ │ +    client(Server_Node).
    │ │ │  
    │ │ │ -client(Server_Node) ->
    │ │ │ +client(Server_Node) ->
    │ │ │      receive
    │ │ │          logoff ->
    │ │ │ -            exit(normal);
    │ │ │ -        {message_to, ToName, Message} ->
    │ │ │ -            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
    │ │ │ -            await_result();
    │ │ │ -        {message_from, FromName, Message} ->
    │ │ │ -            io:format("Message from ~p: ~p~n", [FromName, Message])
    │ │ │ +            exit(normal);
    │ │ │ +        {message_to, ToName, Message} ->
    │ │ │ +            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
    │ │ │ +            await_result();
    │ │ │ +        {message_from, FromName, Message} ->
    │ │ │ +            io:format("Message from ~p: ~p~n", [FromName, Message])
    │ │ │      end,
    │ │ │ -    client(Server_Node).
    │ │ │ +    client(Server_Node).
    │ │ │  
    │ │ │  %%% wait for a response from the server
    │ │ │ -await_result() ->
    │ │ │ +await_result() ->
    │ │ │      receive
    │ │ │ -        {messenger, stop, Why} -> % Stop the client
    │ │ │ -            io:format("~p~n", [Why]),
    │ │ │ -            exit(normal);
    │ │ │ -        {messenger, What} ->  % Normal response
    │ │ │ -            io:format("~p~n", [What])
    │ │ │ +        {messenger, stop, Why} -> % Stop the client
    │ │ │ +            io:format("~p~n", [Why]),
    │ │ │ +            exit(normal);
    │ │ │ +        {messenger, What} ->  % Normal response
    │ │ │ +            io:format("~p~n", [What])
    │ │ │      after 5000 ->
    │ │ │ -            io:format("No response from server~n", []),
    │ │ │ -            exit(timeout)
    │ │ │ +            io:format("No response from server~n", []),
    │ │ │ +            exit(timeout)
    │ │ │      end.

    The following changes are added:

    The messenger server traps exits. If it receives an exit signal, │ │ │ {'EXIT',From,Reason}, this means that a client process has terminated or is │ │ │ unreachable for one of the following reasons:

    • The user has logged off (the "logoff" message is removed).
    • The network connection to the client is broken.
    • The node on which the client process resides has gone down.
    • The client processes has done some illegal operation.

    If an exit signal is received as above, the tuple {From,Name} is deleted from │ │ │ the servers User_List using the server_logoff function. If the node on which │ │ │ the server runs goes down, an exit signal (automatically generated by the │ │ │ system) is sent to all of the client processes: │ │ │ {'EXIT',MessengerPID,noconnection} causing all the client processes to │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/seq_prog.html │ │ │ @@ -136,293 +136,293 @@ │ │ │ 7 │ │ │ 2>

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

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

    Here is a bit more complex calculation:

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

    Here is a bit more complex calculation:

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

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

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

    The following output is shown:

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

    Type a to leave the Erlang system.

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

    3> halt().
    │ │ │ +$

    Type a to leave the Erlang system.

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

    3> halt().
    │ │ │  $

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

    │ │ │

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

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

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

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

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

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

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

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

    Now run the program:

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

    Now run the program:

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

    As expected, double of 10 is 20.

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

    -module(tut).

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

    -module(tut).

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

    4> tut:double(10).

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

    -export([double/1]).

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

    4> tut:double(10).

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

    -export([double/1]).

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

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

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

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

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

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

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

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

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

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

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

    fac(1) ->
    │ │ │      1;

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

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

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

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

    Compile the file:

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

    And now calculate the factorial of 4.

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

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

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

    Compile the file:

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

    And now calculate the factorial of 4.

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

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

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

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

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

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

    Compile:

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

    Try out the new function mult:

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

    Compile:

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

    Try out the new function mult:

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

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

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

    │ │ │

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

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

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

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

    Compile:

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

    Test:

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

    Compile:

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

    Test:

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

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

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

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

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

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

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

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

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

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

    │ │ │ -

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

    tut2:convert(3, inch).

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

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

    tut2:convert(3, inch).

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

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

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

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

    Compile and test:

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

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

    Compile and test:

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

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

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

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

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

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

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

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

    │ │ │

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

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

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

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

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

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

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

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

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

    Another example:

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

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

    Another example:

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

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

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

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

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

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

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

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

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

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

    Compile and test:

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

    Explanation:

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

    The length of an empty list is obviously 0.

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

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

    Compile and test:

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

    Explanation:

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

    The length of an empty list is obviously 0.

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

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

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

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

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

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

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

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

    │ │ │

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

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

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

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

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

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

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

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

    Compile and test:

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

    This example warrants some explanation:

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

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

    Compile and test:

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

    This example warrants some explanation:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    │ │ │

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

    │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    This is possibly a little clearer.

    │ │ │ + list_max(Rest, New_result_far);

    This is possibly a little clearer.

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

    │ │ │ -

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    Test the function:

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

    Explanation:

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

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

    Test the function:

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

    Explanation:

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

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

    [Converted_City | convert_list_to_c(Rest)];

    or:

    [City | convert_list_to_c(Rest)];

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

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

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

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

    [Converted_City | convert_list_to_c(Rest)];

    or:

    [City | convert_list_to_c(Rest)];

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

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

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

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

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

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

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

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

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

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

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

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

    Testing this program gives:

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

    Testing this program gives:

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

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

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

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

    The same program can also be written as:

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

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

    The same program can also be written as:

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

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

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

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

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

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

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

    │ │ │

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

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

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

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

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

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

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

    74> 2004 rem 400.
    │ │ │ -4

    So instead of writing:

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

    So instead of writing:

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

    it can be written:

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

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

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

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

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

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

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

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

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

    │ │ │

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    lists:map(fun convert_to_c/1, List)

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

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

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

    lists:map(fun convert_to_c/1, List)

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

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

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

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

    In sort the fun is used:

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

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

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

    In sort the fun is used:

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

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

    │ │ │
    │ │ │ │ │ │
    │ │ │
    │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/spec_proc.html │ │ │ @@ -123,72 +123,72 @@ │ │ │ │ │ │ │ │ │ │ │ │ Simple Debugging │ │ │ │ │ │

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

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

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

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

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

    │ │ │

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

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

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

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

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

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

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

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

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

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

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

    │ │ │

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

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

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

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

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

    ch4:start_link/0 calls proc_lib:start_link/3, which takes a module │ │ │ +ch4:start_link():

    start_link() ->
    │ │ │ +    proc_lib:start_link(ch4, init, [self()]).

    ch4:start_link/0 calls proc_lib:start_link/3, which takes a module │ │ │ name, a function name, and an argument list as arguments. It then │ │ │ spawns a new process and establishes a link. The new process starts │ │ │ by executing the given function, here ch4:init(Pid), where Pid is │ │ │ the pid of the parent process (obtained by the call to │ │ │ self() in the call to proc_lib:start_link/3).

    All initialization, including name registration, is done in init/1. The new │ │ │ -process has to acknowledge that it has been started to the parent:

    init(Parent) ->
    │ │ │ +process has to acknowledge that it has been started to the parent:

    init(Parent) ->
    │ │ │      ...
    │ │ │ -    proc_lib:init_ack(Parent, {ok, self()}),
    │ │ │ -    loop(...).

    proc_lib:start_link/3 is synchronous and does not return until │ │ │ + proc_lib:init_ack(Parent, {ok, self()}), │ │ │ + loop(...).

    proc_lib:start_link/3 is synchronous and does not return until │ │ │ proc_lib:init_ack/1,2 or │ │ │ proc_lib:init_fail/2,3 has been called, │ │ │ or the process has exited.

    │ │ │ │ │ │ │ │ │ │ │ │ Debugging │ │ │

    │ │ │

    To support the debug facilities in sys, a debug structure is needed. The │ │ │ -Deb term is initialized using sys:debug_options/1:

    init(Parent) ->
    │ │ │ +Deb term is initialized using sys:debug_options/1:

    init(Parent) ->
    │ │ │      ...
    │ │ │ -    Deb = sys:debug_options([]),
    │ │ │ +    Deb = sys:debug_options([]),
    │ │ │      ...
    │ │ │ -    loop(Chs, Parent, Deb).

    sys:debug_options/1 takes a list of options. Given an empty list as in this │ │ │ + loop(Chs, Parent, Deb).

    sys:debug_options/1 takes a list of options. Given an empty list as in this │ │ │ example means that debugging is initially disabled. For information about the │ │ │ possible options, see sys in STDLIB.

    For each system event to be logged or traced, the following function │ │ │ -is to be called:

    sys:handle_debug(Deb, Func, Info, Event) => Deb1

    The arguments have the follow meaning:

    • Deb is the debug structure as returned from sys:debug_options/1.
    • Func is a fun specifying a (user-defined) function used to format trace │ │ │ +is to be called:

      sys:handle_debug(Deb, Func, Info, Event) => Deb1

      The arguments have the follow meaning:

      • Deb is the debug structure as returned from sys:debug_options/1.
      • Func is a fun specifying a (user-defined) function used to format trace │ │ │ output. For each system event, the format function is called as │ │ │ Func(Dev, Event, Info), where:
        • Dev is the I/O device to which the output is to be printed. See io │ │ │ in STDLIB.
        • Event and Info are passed as-is from the call to sys:handle_debug/4.
      • Info is used to pass more information to Func. It can be any term, and it │ │ │ is passed as-is.
      • Event is the system event. It is up to the user to define what a system │ │ │ event is and how it is to be represented. Typically, at least incoming and │ │ │ outgoing messages are considered system events and represented by the tuples │ │ │ {in,Msg[,From]} and {out,Msg,To[,State]}, respectively.

      sys:handle_debug/4 returns an updated debug structure Deb1.

      In the example, sys:handle_debug/4 is called for each incoming and │ │ │ outgoing message. The format function Func is the function │ │ │ -ch4:write_debug/3, which prints the message using io:format/3.

      loop(Chs, Parent, Deb) ->
      │ │ │ +ch4:write_debug/3, which prints the message using io:format/3.

      loop(Chs, Parent, Deb) ->
      │ │ │      receive
      │ │ │ -        {From, alloc} ->
      │ │ │ -            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
      │ │ │ -                                    ch4, {in, alloc, From}),
      │ │ │ -            {Ch, Chs2} = alloc(Chs),
      │ │ │ -            From ! {ch4, Ch},
      │ │ │ -            Deb3 = sys:handle_debug(Deb2, fun ch4:write_debug/3,
      │ │ │ -                                    ch4, {out, {ch4, Ch}, From}),
      │ │ │ -            loop(Chs2, Parent, Deb3);
      │ │ │ -        {free, Ch} ->
      │ │ │ -            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
      │ │ │ -                                    ch4, {in, {free, Ch}}),
      │ │ │ -            Chs2 = free(Ch, Chs),
      │ │ │ -            loop(Chs2, Parent, Deb2);
      │ │ │ +        {From, alloc} ->
      │ │ │ +            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
      │ │ │ +                                    ch4, {in, alloc, From}),
      │ │ │ +            {Ch, Chs2} = alloc(Chs),
      │ │ │ +            From ! {ch4, Ch},
      │ │ │ +            Deb3 = sys:handle_debug(Deb2, fun ch4:write_debug/3,
      │ │ │ +                                    ch4, {out, {ch4, Ch}, From}),
      │ │ │ +            loop(Chs2, Parent, Deb3);
      │ │ │ +        {free, Ch} ->
      │ │ │ +            Deb2 = sys:handle_debug(Deb, fun ch4:write_debug/3,
      │ │ │ +                                    ch4, {in, {free, Ch}}),
      │ │ │ +            Chs2 = free(Ch, Chs),
      │ │ │ +            loop(Chs2, Parent, Deb2);
      │ │ │          ...
      │ │ │      end.
      │ │ │  
      │ │ │ -write_debug(Dev, Event, Name) ->
      │ │ │ -    io:format(Dev, "~p event = ~p~n", [Name, Event]).

      │ │ │ +write_debug(Dev, Event, Name) -> │ │ │ + io:format(Dev, "~p event = ~p~n", [Name, Event]).

      │ │ │ │ │ │ │ │ │ │ │ │ Handling System Messages │ │ │

      │ │ │

      System messages are received as:

      {system, From, Request}

      The content and meaning of these messages are not to be interpreted by the │ │ │ -process. Instead the following function is to be called:

      sys:handle_system_msg(Request, From, Parent, Module, Deb, State)

      The arguments have the following meaning:

      • Request and From from the received system message are to be │ │ │ +process. Instead the following function is to be called:

        sys:handle_system_msg(Request, From, Parent, Module, Deb, State)

        The arguments have the following meaning:

        • Request and From from the received system message are to be │ │ │ passed as-is to the call to sys:handle_system_msg/6.
        • Parent is the pid of the parent process.
        • Module is the name of the module implementing the speciall process.
        • Deb is the debug structure.
        • State is a term describing the internal state and is passed on to │ │ │ Module:system_continue/3, Module:system_terminate/4/ │ │ │ Module:system_get_state/1, and Module:system_replace_state/2.

        sys:handle_system_msg/6 does not return. It handles the system │ │ │ message and eventually calls either of the following functions:

        • Module:system_continue(Parent, Deb, State) - if process execution is to │ │ │ continue.

        • Module:system_terminate(Reason, Parent, Deb, State) - if the │ │ │ process is to terminate.

        While handling the system message, sys:handle_system_msg/6 can call │ │ │ one of the following functions:

        • Module:system_get_state(State) - if the process is to return its state.

        • Module:system_replace_state(StateFun, State) - if the process is │ │ │ to replace its state using the fun StateFun fun. See sys:replace_state/3 │ │ │ for more information.

        • system_code_change(Misc, Module, OldVsn, Extra) - if the process is to │ │ │ perform a code change.

        A process in a supervision tree is expected to terminate with the same reason as │ │ │ -its parent.

        In the example, system messages are handed by the following code:

        loop(Chs, Parent, Deb) ->
        │ │ │ +its parent.

        In the example, system messages are handed by the following code:

        loop(Chs, Parent, Deb) ->
        │ │ │      receive
        │ │ │          ...
        │ │ │  
        │ │ │ -        {system, From, Request} ->
        │ │ │ -            sys:handle_system_msg(Request, From, Parent,
        │ │ │ -                                  ch4, Deb, Chs)
        │ │ │ +        {system, From, Request} ->
        │ │ │ +            sys:handle_system_msg(Request, From, Parent,
        │ │ │ +                                  ch4, Deb, Chs)
        │ │ │      end.
        │ │ │  
        │ │ │ -system_continue(Parent, Deb, Chs) ->
        │ │ │ -    loop(Chs, Parent, Deb).
        │ │ │ +system_continue(Parent, Deb, Chs) ->
        │ │ │ +    loop(Chs, Parent, Deb).
        │ │ │  
        │ │ │ -system_terminate(Reason, Parent, Deb, Chs) ->
        │ │ │ -    exit(Reason).
        │ │ │ +system_terminate(Reason, Parent, Deb, Chs) ->
        │ │ │ +    exit(Reason).
        │ │ │  
        │ │ │ -system_get_state(Chs) ->
        │ │ │ -    {ok, Chs, Chs}.
        │ │ │ +system_get_state(Chs) ->
        │ │ │ +    {ok, Chs, Chs}.
        │ │ │  
        │ │ │ -system_replace_state(StateFun, Chs) ->
        │ │ │ -    NChs = StateFun(Chs),
        │ │ │ -    {ok, NChs, NChs}.

        If a special process is configured to trap exits, it must take notice │ │ │ +system_replace_state(StateFun, Chs) -> │ │ │ + NChs = StateFun(Chs), │ │ │ + {ok, NChs, NChs}.

        If a special process is configured to trap exits, it must take notice │ │ │ of 'EXIT' messages from its parent process and terminate using the │ │ │ -same exit reason once the parent process has terminated.

        Here is an example:

        init(Parent) ->
        │ │ │ +same exit reason once the parent process has terminated.

        Here is an example:

        init(Parent) ->
        │ │ │      ...,
        │ │ │ -    process_flag(trap_exit, true),
        │ │ │ +    process_flag(trap_exit, true),
        │ │ │      ...,
        │ │ │ -    loop(Parent).
        │ │ │ +    loop(Parent).
        │ │ │  
        │ │ │ -loop(Parent) ->
        │ │ │ +loop(Parent) ->
        │ │ │      receive
        │ │ │          ...
        │ │ │ -        {'EXIT', Parent, Reason} ->
        │ │ │ +        {'EXIT', Parent, Reason} ->
        │ │ │              %% Clean up here, if needed.
        │ │ │ -            exit(Reason);
        │ │ │ +            exit(Reason);
        │ │ │          ...
        │ │ │      end.

        │ │ │ │ │ │ │ │ │ │ │ │ User-Defined Behaviours │ │ │

        │ │ │ @@ -448,71 +448,71 @@ │ │ │ function. Note that the -optional_callbacks attribute is to be used together │ │ │ with the -callback attribute; it cannot be combined with the │ │ │ behaviour_info() function described below.

        Tools that need to know about optional callback functions can call │ │ │ Behaviour:behaviour_info(optional_callbacks) to get a list of all optional │ │ │ callback functions.

        Note

        We recommend using the -callback attribute rather than the │ │ │ behaviour_info() function. The reason is that the extra type information can │ │ │ be used by tools to produce documentation or find discrepancies.

        As an alternative to the -callback and -optional_callbacks attributes you │ │ │ -may directly implement and export behaviour_info():

        behaviour_info(callbacks) ->
        │ │ │ -    [{Name1, Arity1},...,{NameN, ArityN}].

        where each {Name, Arity} specifies the name and arity of a callback function. │ │ │ +may directly implement and export behaviour_info():

        behaviour_info(callbacks) ->
        │ │ │ +    [{Name1, Arity1},...,{NameN, ArityN}].

        where each {Name, Arity} specifies the name and arity of a callback function. │ │ │ This function is otherwise automatically generated by the compiler using the │ │ │ -callback attributes.

        When the compiler encounters the module attribute -behaviour(Behaviour). in a │ │ │ module Mod, it calls Behaviour:behaviour_info(callbacks) and compares the │ │ │ result with the set of functions actually exported from Mod, and issues a │ │ │ warning if any callback function is missing.

        Example:

        %% User-defined behaviour module
        │ │ │ --module(simple_server).
        │ │ │ --export([start_link/2, init/3, ...]).
        │ │ │ +-module(simple_server).
        │ │ │ +-export([start_link/2, init/3, ...]).
        │ │ │  
        │ │ │ --callback init(State :: term()) -> 'ok'.
        │ │ │ --callback handle_req(Req :: term(), State :: term()) -> {'ok', Reply :: term()}.
        │ │ │ --callback terminate() -> 'ok'.
        │ │ │ --callback format_state(State :: term()) -> term().
        │ │ │ +-callback init(State :: term()) -> 'ok'.
        │ │ │ +-callback handle_req(Req :: term(), State :: term()) -> {'ok', Reply :: term()}.
        │ │ │ +-callback terminate() -> 'ok'.
        │ │ │ +-callback format_state(State :: term()) -> term().
        │ │ │  
        │ │ │ --optional_callbacks([format_state/1]).
        │ │ │ +-optional_callbacks([format_state/1]).
        │ │ │  
        │ │ │  %% Alternatively you may define:
        │ │ │  %%
        │ │ │  %% -export([behaviour_info/1]).
        │ │ │  %% behaviour_info(callbacks) ->
        │ │ │  %%     [{init,1},
        │ │ │  %%      {handle_req,2},
        │ │ │  %%      {terminate,0}].
        │ │ │  
        │ │ │ -start_link(Name, Module) ->
        │ │ │ -    proc_lib:start_link(?MODULE, init, [self(), Name, Module]).
        │ │ │ +start_link(Name, Module) ->
        │ │ │ +    proc_lib:start_link(?MODULE, init, [self(), Name, Module]).
        │ │ │  
        │ │ │ -init(Parent, Name, Module) ->
        │ │ │ -    register(Name, self()),
        │ │ │ +init(Parent, Name, Module) ->
        │ │ │ +    register(Name, self()),
        │ │ │      ...,
        │ │ │ -    Dbg = sys:debug_options([]),
        │ │ │ -    proc_lib:init_ack(Parent, {ok, self()}),
        │ │ │ -    loop(Parent, Module, Deb, ...).
        │ │ │ +    Dbg = sys:debug_options([]),
        │ │ │ +    proc_lib:init_ack(Parent, {ok, self()}),
        │ │ │ +    loop(Parent, Module, Deb, ...).
        │ │ │  
        │ │ │ -...

        In a callback module:

        -module(db).
        │ │ │ --behaviour(simple_server).
        │ │ │ +...

        In a callback module:

        -module(db).
        │ │ │ +-behaviour(simple_server).
        │ │ │  
        │ │ │ --export([init/1, handle_req/2, terminate/0]).
        │ │ │ +-export([init/1, handle_req/2, terminate/0]).
        │ │ │  
        │ │ │  ...

        The contracts specified with -callback attributes in behaviour modules can be │ │ │ further refined by adding -spec attributes in callback modules. This can be │ │ │ useful as -callback contracts are usually generic. The same callback module │ │ │ -with contracts for the callbacks:

        -module(db).
        │ │ │ --behaviour(simple_server).
        │ │ │ +with contracts for the callbacks:

        -module(db).
        │ │ │ +-behaviour(simple_server).
        │ │ │  
        │ │ │ --export([init/1, handle_req/2, terminate/0]).
        │ │ │ +-export([init/1, handle_req/2, terminate/0]).
        │ │ │  
        │ │ │ --record(state, {field1 :: [atom()], field2 :: integer()}).
        │ │ │ +-record(state, {field1 :: [atom()], field2 :: integer()}).
        │ │ │  
        │ │ │ --type state()   :: #state{}.
        │ │ │ --type request() :: {'store', term(), term()};
        │ │ │ -                   {'lookup', term()}.
        │ │ │ +-type state()   :: #state{}.
        │ │ │ +-type request() :: {'store', term(), term()};
        │ │ │ +                   {'lookup', term()}.
        │ │ │  
        │ │ │  ...
        │ │ │  
        │ │ │ --spec handle_req(request(), state()) -> {'ok', term()}.
        │ │ │ +-spec handle_req(request(), state()) -> {'ok', term()}.
        │ │ │  
        │ │ │  ...

        Each -spec contract is to be a subtype of the respective -callback contract.

        │ │ │ │ │ │ │ │ │
        │ │ │
        │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/statem.html │ │ │ @@ -124,15 +124,15 @@ │ │ │ │ │ │

        Established Automata Theory does not deal much with how a state transition │ │ │ is triggered, but assumes that the output is a function of the input │ │ │ (and the state) and that they are some kind of values.

        For an Event-Driven State Machine, the input is an event that triggers │ │ │ a state transition and the output is actions executed during │ │ │ the state transition. Analogously to the mathematical model │ │ │ of a Finite State Machine, it can be described as a set of relations │ │ │ -of the following form:

        State(S) x Event(E) -> Actions(A), State(S')

        These relations are interpreted as follows: if we are in state S, │ │ │ +of the following form:

        State(S) x Event(E) -> Actions(A), State(S')

        These relations are interpreted as follows: if we are in state S, │ │ │ and event E occurs, we are to perform actions A, and make a transition │ │ │ to state S'. Notice that S' can be equal to S, │ │ │ and that A can be empty.

        In gen_statem we define a state change as a state transition in which the │ │ │ new state S' is different from the current state S, where "different" means │ │ │ Erlang's strict inequality: =/= also known as "does not match". gen_statem │ │ │ does more things during state changes than during other state transitions.

        As A and S' depend only on S and E, the kind of state machine described │ │ │ here is a Mealy machine (see, for example, the Wikipedia article │ │ │ @@ -405,20 +405,20 @@ │ │ │ │ │ │ State Enter Calls │ │ │ │ │ │

        The gen_statem behaviour can, if this is enabled, regardless of callback │ │ │ mode, automatically call the state callback │ │ │ with special arguments whenever the state changes, so you can write │ │ │ state enter actions near the rest of the state transition rules. │ │ │ -It typically looks like this:

        StateName(enter, OldState, Data) ->
        │ │ │ +It typically looks like this:

        StateName(enter, OldState, Data) ->
        │ │ │      ... code for state enter actions here ...
        │ │ │ -    {keep_state, NewData};
        │ │ │ -StateName(EventType, EventContent, Data) ->
        │ │ │ +    {keep_state, NewData};
        │ │ │ +StateName(EventType, EventContent, Data) ->
        │ │ │      ... code for actions here ...
        │ │ │ -    {next_state, NewStateName, NewData}.

        Since the state enter call is not an event there are restrictions on the │ │ │ + {next_state, NewStateName, NewData}.

        Since the state enter call is not an event there are restrictions on the │ │ │ allowed return value and state transition actions. │ │ │ You must not change the state, postpone this non-event, │ │ │ insert any events, or change the │ │ │ callback module.

        The first state that is entered after gen_statem:init/1 will get │ │ │ a state enter call with OldState equal to the current state.

        You may repeat the state enter call using the {repeat_state,...} return │ │ │ value from the state callback. In this case │ │ │ OldState will also be equal to the current state.

        Depending on how your state machine is specified, this can be a very useful │ │ │ @@ -499,72 +499,72 @@ │ │ │ │ │ │ locked --> check_code : {button, Button}\n* Collect Buttons │ │ │ check_code --> locked : Incorrect code │ │ │ check_code --> open : Correct code\n* do_unlock()\n* Clear Buttons\n* Set state_timeout 10 s │ │ │ │ │ │ open --> open : {button, Digit} │ │ │ open --> locked : state_timeout\n* do_lock()

        This code lock state machine can be implemented using gen_statem with │ │ │ -the following callback module:

        -module(code_lock).
        │ │ │ --behaviour(gen_statem).
        │ │ │ --define(NAME, code_lock).
        │ │ │ +the following callback module:

        -module(code_lock).
        │ │ │ +-behaviour(gen_statem).
        │ │ │ +-define(NAME, code_lock).
        │ │ │  
        │ │ │ --export([start_link/1]).
        │ │ │ --export([button/1]).
        │ │ │ --export([init/1,callback_mode/0,terminate/3]).
        │ │ │ --export([locked/3,open/3]).
        │ │ │ -
        │ │ │ -start_link(Code) ->
        │ │ │ -    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
        │ │ │ -
        │ │ │ -button(Button) ->
        │ │ │ -    gen_statem:cast(?NAME, {button,Button}).
        │ │ │ -
        │ │ │ -init(Code) ->
        │ │ │ -    do_lock(),
        │ │ │ -    Data = #{code => Code, length => length(Code), buttons => []},
        │ │ │ -    {ok, locked, Data}.
        │ │ │ -
        │ │ │ -callback_mode() ->
        │ │ │ -    state_functions.
        locked(
        │ │ │ -  cast, {button,Button},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +-export([start_link/1]).
        │ │ │ +-export([button/1]).
        │ │ │ +-export([init/1,callback_mode/0,terminate/3]).
        │ │ │ +-export([locked/3,open/3]).
        │ │ │ +
        │ │ │ +start_link(Code) ->
        │ │ │ +    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
        │ │ │ +
        │ │ │ +button(Button) ->
        │ │ │ +    gen_statem:cast(?NAME, {button,Button}).
        │ │ │ +
        │ │ │ +init(Code) ->
        │ │ │ +    do_lock(),
        │ │ │ +    Data = #{code => Code, length => length(Code), buttons => []},
        │ │ │ +    {ok, locked, Data}.
        │ │ │ +
        │ │ │ +callback_mode() ->
        │ │ │ +    state_functions.
        locked(
        │ │ │ +  cast, {button,Button},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │      NewButtons =
        │ │ │          if
        │ │ │ -            length(Buttons) < Length ->
        │ │ │ +            length(Buttons) < Length ->
        │ │ │                  Buttons;
        │ │ │              true ->
        │ │ │ -                tl(Buttons)
        │ │ │ -        end ++ [Button],
        │ │ │ +                tl(Buttons)
        │ │ │ +        end ++ [Button],
        │ │ │      if
        │ │ │          NewButtons =:= Code -> % Correct
        │ │ │ -	    do_unlock(),
        │ │ │ -            {next_state, open, Data#{buttons := []},
        │ │ │ -             [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ +	    do_unlock(),
        │ │ │ +            {next_state, open, Data#{buttons := []},
        │ │ │ +             [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │  	true -> % Incomplete | Incorrect
        │ │ │ -            {next_state, locked, Data#{buttons := NewButtons}}
        │ │ │ -    end.
        open(state_timeout, lock,  Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {next_state, locked, Data};
        │ │ │ -open(cast, {button,_}, Data) ->
        │ │ │ -    {next_state, open, Data}.
        do_lock() ->
        │ │ │ -    io:format("Lock~n", []).
        │ │ │ -do_unlock() ->
        │ │ │ -    io:format("Unlock~n", []).
        │ │ │ +            {next_state, locked, Data#{buttons := NewButtons}}
        │ │ │ +    end.
        open(state_timeout, lock,  Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {next_state, locked, Data};
        │ │ │ +open(cast, {button,_}, Data) ->
        │ │ │ +    {next_state, open, Data}.
        do_lock() ->
        │ │ │ +    io:format("Lock~n", []).
        │ │ │ +do_unlock() ->
        │ │ │ +    io:format("Unlock~n", []).
        │ │ │  
        │ │ │ -terminate(_Reason, State, _Data) ->
        │ │ │ -    State =/= locked andalso do_lock(),
        │ │ │ +terminate(_Reason, State, _Data) ->
        │ │ │ +    State =/= locked andalso do_lock(),
        │ │ │      ok.

        The code is explained in the next sections.

        │ │ │ │ │ │ │ │ │ │ │ │ Starting gen_statem │ │ │

        │ │ │

        In the example in the previous section, gen_statem is started by calling │ │ │ -code_lock:start_link(Code):

        start_link(Code) ->
        │ │ │ -    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).

        start_link/1 calls function gen_statem:start_link/4, │ │ │ +code_lock:start_link(Code):

        start_link(Code) ->
        │ │ │ +    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).

        start_link/1 calls function gen_statem:start_link/4, │ │ │ which spawns and links to a new process, a gen_statem.

        • The first argument, {local,?NAME}, specifies the name. In this case, the │ │ │ gen_statem is locally registered as code_lock through the macro ?NAME.

          If the name is omitted, the gen_statem is not registered. Instead its pid │ │ │ must be used. The name can also be specified as {global, Name}, then the │ │ │ gen_statem is registered using global:register_name/2 in Kernel.

        • The second argument, ?MODULE, is the name of the callback module, │ │ │ that is, the module where the callback functions are located, │ │ │ which is this module.

          The interface functions (start_link/1 and button/1) are located in the │ │ │ same module as the callback functions (init/1, locked/3, and open/3). │ │ │ @@ -574,184 +574,184 @@ │ │ │ see gen_statem:start_link/3.

        If name registration succeeds, the new gen_statem process calls callback │ │ │ function code_lock:init(Code). This function is expected to return │ │ │ {ok, State, Data}, where State is the initial state of the gen_statem, │ │ │ in this case locked; assuming that the door is locked to begin with. │ │ │ Data is the internal server data of the gen_statem. Here the server data │ │ │ is a map() with key code that stores the correct │ │ │ button sequence, key length store its length, and key buttons │ │ │ -that stores the collected buttons up to the same length.

        init(Code) ->
        │ │ │ -    do_lock(),
        │ │ │ -    Data = #{code => Code, length => length(Code), buttons => []},
        │ │ │ -    {ok, locked, Data}.

        Function gen_statem:start_link/3,4 │ │ │ +that stores the collected buttons up to the same length.

        init(Code) ->
        │ │ │ +    do_lock(),
        │ │ │ +    Data = #{code => Code, length => length(Code), buttons => []},
        │ │ │ +    {ok, locked, Data}.

        Function gen_statem:start_link/3,4 │ │ │ is synchronous. It does not return until the gen_statem is initialized │ │ │ and is ready to receive events.

        Function gen_statem:start_link/3,4 │ │ │ must be used if the gen_statem is part of a supervision tree, that is, │ │ │ started by a supervisor. Function, │ │ │ gen_statem:start/3,4 can be used to start │ │ │ a standalone gen_statem, meaning it is not part of a supervision tree.

        Function Module:callback_mode/0 selects │ │ │ the CallbackMode for the callback module, │ │ │ in this case state_functions. │ │ │ -That is, each state has its own handler function:

        callback_mode() ->
        │ │ │ +That is, each state has its own handler function:

        callback_mode() ->
        │ │ │      state_functions.

        │ │ │ │ │ │ │ │ │ │ │ │ Handling Events │ │ │

        │ │ │

        The function notifying the code lock about a button event is implemented using │ │ │ -gen_statem:cast/2:

        button(Button) ->
        │ │ │ -    gen_statem:cast(?NAME, {button,Button}).

        The first argument is the name of the gen_statem and must agree with │ │ │ +gen_statem:cast/2:

        button(Button) ->
        │ │ │ +    gen_statem:cast(?NAME, {button,Button}).

        The first argument is the name of the gen_statem and must agree with │ │ │ the name used to start it. So, we use the same macro ?NAME as when starting. │ │ │ {button, Button} is the event content.

        The event is sent to the gen_statem. When the event is received, the │ │ │ gen_statem calls StateName(cast, Event, Data), which is expected │ │ │ to return a tuple {next_state, NewStateName, NewData}, or │ │ │ {next_state, NewStateName, NewData, Actions}. StateName is the name │ │ │ of the current state and NewStateName is the name of the next state. │ │ │ NewData is a new value for the server data of the gen_statem, │ │ │ -and Actions is a list of actions to be performed by the gen_statem engine.

        locked(
        │ │ │ -  cast, {button,Button},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +and Actions is a list of actions to be performed by the gen_statem engine.

        locked(
        │ │ │ +  cast, {button,Button},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │      NewButtons =
        │ │ │          if
        │ │ │ -            length(Buttons) < Length ->
        │ │ │ +            length(Buttons) < Length ->
        │ │ │                  Buttons;
        │ │ │              true ->
        │ │ │ -                tl(Buttons)
        │ │ │ -        end ++ [Button],
        │ │ │ +                tl(Buttons)
        │ │ │ +        end ++ [Button],
        │ │ │      if
        │ │ │          NewButtons =:= Code -> % Correct
        │ │ │ -	    do_unlock(),
        │ │ │ -            {next_state, open, Data#{buttons := []},
        │ │ │ -             [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ +	    do_unlock(),
        │ │ │ +            {next_state, open, Data#{buttons := []},
        │ │ │ +             [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │  	true -> % Incomplete | Incorrect
        │ │ │ -            {next_state, locked, Data#{buttons := NewButtons}}
        │ │ │ +            {next_state, locked, Data#{buttons := NewButtons}}
        │ │ │      end.

        In state locked, when a button is pressed, it is collected with the │ │ │ previously pressed buttons up to the length of the correct code, then │ │ │ compared with the correct code. Depending on the result, the door is │ │ │ either unlocked and the gen_statem goes to state open, or the door │ │ │ remains in state locked.

        When changing to state open, the collected buttons are reset, the lock │ │ │ -unlocked, and a state time-out for 10 seconds is started.

        open(cast, {button,_}, Data) ->
        │ │ │ -    {next_state, open, Data}.

        In state open, a button event is ignored by staying in the same state. │ │ │ +unlocked, and a state time-out for 10 seconds is started.

        open(cast, {button,_}, Data) ->
        │ │ │ +    {next_state, open, Data}.

        In state open, a button event is ignored by staying in the same state. │ │ │ This can also be done by returning {keep_state, Data}, or in this case │ │ │ since Data is unchanged, by returning keep_state_and_data.

        │ │ │ │ │ │ │ │ │ │ │ │ State Time-Outs │ │ │

        │ │ │

        When a correct code has been given, the door is unlocked and the following │ │ │ -tuple is returned from locked/2:

        {next_state, open, Data#{buttons := []},
        │ │ │ - [{state_timeout,10_000,lock}]}; % Time in milliseconds

        10,000 is a time-out value in milliseconds. After this time (10 seconds), │ │ │ +tuple is returned from locked/2:

        {next_state, open, Data#{buttons := []},
        │ │ │ + [{state_timeout,10_000,lock}]}; % Time in milliseconds

        10,000 is a time-out value in milliseconds. After this time (10 seconds), │ │ │ a time-out occurs. Then, StateName(state_timeout, lock, Data) is called. │ │ │ The time-out occurs when the door has been in state open for 10 seconds. │ │ │ -After that the door is locked again:

        open(state_timeout, lock,  Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {next_state, locked, Data};

        The timer for a state time-out is automatically canceled when │ │ │ +After that the door is locked again:

        open(state_timeout, lock,  Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {next_state, locked, Data};

        The timer for a state time-out is automatically canceled when │ │ │ the state machine does a state change.

        You can restart, cancel, or update a state time-out. See section │ │ │ Time-Outs for details.

        │ │ │ │ │ │ │ │ │ │ │ │ All State Events │ │ │

        │ │ │

        Sometimes events can arrive in any state of the gen_statem. It is convenient │ │ │ to handle these in a common state handler function that all state functions │ │ │ call for events not specific to the state.

        Consider a code_length/0 function that returns the length │ │ │ of the correct code. We dispatch all events that are not state-specific │ │ │ to the common function handle_common/3:

        ...
        │ │ │ --export([button/1,code_length/0]).
        │ │ │ +-export([button/1,code_length/0]).
        │ │ │  ...
        │ │ │  
        │ │ │ -code_length() ->
        │ │ │ -    gen_statem:call(?NAME, code_length).
        │ │ │ +code_length() ->
        │ │ │ +    gen_statem:call(?NAME, code_length).
        │ │ │  
        │ │ │  ...
        │ │ │ -locked(...) -> ... ;
        │ │ │ -locked(EventType, EventContent, Data) ->
        │ │ │ -    handle_common(EventType, EventContent, Data).
        │ │ │ +locked(...) -> ... ;
        │ │ │ +locked(EventType, EventContent, Data) ->
        │ │ │ +    handle_common(EventType, EventContent, Data).
        │ │ │  
        │ │ │  ...
        │ │ │ -open(...) -> ... ;
        │ │ │ -open(EventType, EventContent, Data) ->
        │ │ │ -    handle_common(EventType, EventContent, Data).
        │ │ │ -
        │ │ │ -handle_common({call,From}, code_length, #{code := Code} = Data) ->
        │ │ │ -    {keep_state, Data,
        │ │ │ -     [{reply,From,length(Code)}]}.

        Another way to do it is through a convenience macro ?HANDLE_COMMON/0:

        ...
        │ │ │ --export([button/1,code_length/0]).
        │ │ │ +open(...) -> ... ;
        │ │ │ +open(EventType, EventContent, Data) ->
        │ │ │ +    handle_common(EventType, EventContent, Data).
        │ │ │ +
        │ │ │ +handle_common({call,From}, code_length, #{code := Code} = Data) ->
        │ │ │ +    {keep_state, Data,
        │ │ │ +     [{reply,From,length(Code)}]}.

        Another way to do it is through a convenience macro ?HANDLE_COMMON/0:

        ...
        │ │ │ +-export([button/1,code_length/0]).
        │ │ │  ...
        │ │ │  
        │ │ │ -code_length() ->
        │ │ │ -    gen_statem:call(?NAME, code_length).
        │ │ │ +code_length() ->
        │ │ │ +    gen_statem:call(?NAME, code_length).
        │ │ │  
        │ │ │ --define(HANDLE_COMMON,
        │ │ │ -    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, D)).
        │ │ │ +-define(HANDLE_COMMON,
        │ │ │ +    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, D)).
        │ │ │  %%
        │ │ │ -handle_common({call,From}, code_length, #{code := Code} = Data) ->
        │ │ │ -    {keep_state, Data,
        │ │ │ -     [{reply,From,length(Code)}]}.
        │ │ │ +handle_common({call,From}, code_length, #{code := Code} = Data) ->
        │ │ │ +    {keep_state, Data,
        │ │ │ +     [{reply,From,length(Code)}]}.
        │ │ │  
        │ │ │  ...
        │ │ │ -locked(...) -> ... ;
        │ │ │ +locked(...) -> ... ;
        │ │ │  ?HANDLE_COMMON.
        │ │ │  
        │ │ │  ...
        │ │ │ -open(...) -> ... ;
        │ │ │ +open(...) -> ... ;
        │ │ │  ?HANDLE_COMMON.

        This example uses gen_statem:call/2, which waits for a reply from the server. │ │ │ The reply is sent with a {reply, From, Reply} tuple in an action list in the │ │ │ {keep_state, ...} tuple that retains the current state. This return form is │ │ │ convenient when you want to stay in the current state but do not know or care │ │ │ about what it is.

        If the common state callback needs to know the current state a function │ │ │ -handle_common/4 can be used instead:

        -define(HANDLE_COMMON,
        │ │ │ -    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, ?FUNCTION_NAME, D)).

        │ │ │ +handle_common/4 can be used instead:

        -define(HANDLE_COMMON,
        │ │ │ +    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, ?FUNCTION_NAME, D)).

        │ │ │ │ │ │ │ │ │ │ │ │ One State Callback │ │ │

        │ │ │

        If callback mode handle_event_function is used, │ │ │ all events are handled in │ │ │ Module:handle_event/4 and we can │ │ │ (but do not have to) use an event-centered approach where we first branch │ │ │ depending on event and then depending on state:

        ...
        │ │ │ --export([handle_event/4]).
        │ │ │ +-export([handle_event/4]).
        │ │ │  
        │ │ │  ...
        │ │ │ -callback_mode() ->
        │ │ │ +callback_mode() ->
        │ │ │      handle_event_function.
        │ │ │  
        │ │ │ -handle_event(cast, {button,Button}, State, #{code := Code} = Data) ->
        │ │ │ +handle_event(cast, {button,Button}, State, #{code := Code} = Data) ->
        │ │ │      case State of
        │ │ │  	locked ->
        │ │ │ -            #{length := Length, buttons := Buttons} = Data,
        │ │ │ +            #{length := Length, buttons := Buttons} = Data,
        │ │ │              NewButtons =
        │ │ │                  if
        │ │ │ -                    length(Buttons) < Length ->
        │ │ │ +                    length(Buttons) < Length ->
        │ │ │                          Buttons;
        │ │ │                      true ->
        │ │ │ -                        tl(Buttons)
        │ │ │ -                end ++ [Button],
        │ │ │ +                        tl(Buttons)
        │ │ │ +                end ++ [Button],
        │ │ │              if
        │ │ │                  NewButtons =:= Code -> % Correct
        │ │ │ -                    do_unlock(),
        │ │ │ -                    {next_state, open, Data#{buttons := []},
        │ │ │ -                     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ +                    do_unlock(),
        │ │ │ +                    {next_state, open, Data#{buttons := []},
        │ │ │ +                     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │                  true -> % Incomplete | Incorrect
        │ │ │ -                    {keep_state, Data#{buttons := NewButtons}}
        │ │ │ +                    {keep_state, Data#{buttons := NewButtons}}
        │ │ │              end;
        │ │ │  	open ->
        │ │ │              keep_state_and_data
        │ │ │      end;
        │ │ │ -handle_event(state_timeout, lock, open, Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {next_state, locked, Data};
        │ │ │ -handle_event(
        │ │ │ -  {call,From}, code_length, _State, #{code := Code} = Data) ->
        │ │ │ -    {keep_state, Data,
        │ │ │ -     [{reply,From,length(Code)}]}.
        │ │ │ +handle_event(state_timeout, lock, open, Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {next_state, locked, Data};
        │ │ │ +handle_event(
        │ │ │ +  {call,From}, code_length, _State, #{code := Code} = Data) ->
        │ │ │ +    {keep_state, Data,
        │ │ │ +     [{reply,From,length(Code)}]}.
        │ │ │  
        │ │ │  ...

        │ │ │ │ │ │ │ │ │ │ │ │ Stopping │ │ │

        │ │ │ @@ -763,59 +763,59 @@ │ │ │ │ │ │

        If the gen_statem is part of a supervision tree, no stop function is needed. │ │ │ The gen_statem is automatically terminated by its supervisor. Exactly how │ │ │ this is done is defined by a shutdown strategy │ │ │ set in the supervisor.

        If it is necessary to clean up before termination, the shutdown strategy │ │ │ must be a time-out value and the gen_statem must in function init/1 │ │ │ set itself to trap exit signals by calling │ │ │ -process_flag(trap_exit, true):

        init(Args) ->
        │ │ │ -    process_flag(trap_exit, true),
        │ │ │ -    do_lock(),
        │ │ │ +process_flag(trap_exit, true):

        init(Args) ->
        │ │ │ +    process_flag(trap_exit, true),
        │ │ │ +    do_lock(),
        │ │ │      ...

        When ordered to shut down, the gen_statem then calls callback function │ │ │ terminate(shutdown, State, Data).

        In this example, function terminate/3 locks the door if it is open, │ │ │ so we do not accidentally leave the door open │ │ │ -when the supervision tree terminates:

        terminate(_Reason, State, _Data) ->
        │ │ │ -    State =/= locked andalso do_lock(),
        │ │ │ +when the supervision tree terminates:

        terminate(_Reason, State, _Data) ->
        │ │ │ +    State =/= locked andalso do_lock(),
        │ │ │      ok.

        │ │ │ │ │ │ │ │ │ │ │ │ Standalone gen_statem │ │ │

        │ │ │

        If the gen_statem is not part of a supervision tree, it can be stopped │ │ │ using gen_statem:stop/1, preferably through │ │ │ an API function:

        ...
        │ │ │ --export([start_link/1,stop/0]).
        │ │ │ +-export([start_link/1,stop/0]).
        │ │ │  
        │ │ │  ...
        │ │ │ -stop() ->
        │ │ │ -    gen_statem:stop(?NAME).

        This makes the gen_statem call callback function terminate/3 just like │ │ │ +stop() -> │ │ │ + gen_statem:stop(?NAME).

        This makes the gen_statem call callback function terminate/3 just like │ │ │ for a supervised server and waits for the process to terminate.

        │ │ │ │ │ │ │ │ │ │ │ │ Event Time-Outs │ │ │

        │ │ │

        A time-out feature inherited from gen_statem's predecessor gen_fsm, │ │ │ is an event time-out, that is, if an event arrives the timer is canceled. │ │ │ You get either an event or a time-out, but not both.

        It is ordered by the │ │ │ transition action {timeout, Time, EventContent}, │ │ │ or just an integer Time, even without the enclosing actions list (the latter │ │ │ is a form inherited from gen_fsm).

        This type of time-out is useful, for example, to act on inactivity. │ │ │ Let's restart the code sequence if no button is pressed for say 30 seconds:

        ...
        │ │ │  
        │ │ │ -locked(timeout, _, Data) ->
        │ │ │ -    {next_state, locked, Data#{buttons := []}};
        │ │ │ -locked(
        │ │ │ -  cast, {button,Button},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +locked(timeout, _, Data) ->
        │ │ │ +    {next_state, locked, Data#{buttons := []}};
        │ │ │ +locked(
        │ │ │ +  cast, {button,Button},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │  ...
        │ │ │  	true -> % Incomplete | Incorrect
        │ │ │ -            {next_state, locked, Data#{buttons := NewButtons},
        │ │ │ -             30_000} % Time in milliseconds
        │ │ │ +            {next_state, locked, Data#{buttons := NewButtons},
        │ │ │ +             30_000} % Time in milliseconds
        │ │ │  ...

        Whenever we receive a button event we start an event time-out of 30 seconds, │ │ │ and if we get an event type of timeout we reset the remaining │ │ │ code sequence.

        An event time-out is canceled by any other event so you either get │ │ │ some other event or the time-out event. Therefore, canceling, │ │ │ restarting, or updating an event time-out is neither possible nor │ │ │ necessary. Whatever event you act on has already canceled │ │ │ the event time-out, so there is never a running event time-out │ │ │ @@ -834,30 +834,30 @@ │ │ │ another, maybe cancel the time-out without changing states, or perhaps run │ │ │ multiple time-outs in parallel. All this can be accomplished with │ │ │ generic time-outs. They may look a little │ │ │ bit like event time-outs but contain │ │ │ a name to allow for any number of them simultaneously and they are │ │ │ not automatically canceled.

        Here is how to accomplish the state time-out in the previous example │ │ │ by instead using a generic time-out named for example open:

        ...
        │ │ │ -locked(
        │ │ │ -  cast, {button,Button},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +locked(
        │ │ │ +  cast, {button,Button},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │  ...
        │ │ │      if
        │ │ │          NewButtons =:= Code -> % Correct
        │ │ │ -	    do_unlock(),
        │ │ │ -            {next_state, open, Data#{buttons := []},
        │ │ │ -             [{{timeout,open},10_000,lock}]}; % Time in milliseconds
        │ │ │ +	    do_unlock(),
        │ │ │ +            {next_state, open, Data#{buttons := []},
        │ │ │ +             [{{timeout,open},10_000,lock}]}; % Time in milliseconds
        │ │ │  ...
        │ │ │  
        │ │ │ -open({timeout,open}, lock, Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {next_state,locked,Data};
        │ │ │ -open(cast, {button,_}, Data) ->
        │ │ │ -    {keep_state,Data};
        │ │ │ +open({timeout,open}, lock, Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {next_state,locked,Data};
        │ │ │ +open(cast, {button,_}, Data) ->
        │ │ │ +    {keep_state,Data};
        │ │ │  ...

        Specific generic time-outs can just as state time-outs │ │ │ be restarted or canceled by setting it to a new time or infinity.

        In this particular case we do not need to cancel the time-out since │ │ │ the time-out event is the only possible reason to do a state change │ │ │ from open to locked.

        Instead of bothering with when to cancel a time-out, a late time-out event │ │ │ can be handled by ignoring it if it arrives in a state │ │ │ where it is known to be late.

        You can restart, cancel, or update a generic time-out. │ │ │ See section Time-Outs for details.

        │ │ │ @@ -869,32 +869,32 @@ │ │ │

        The most versatile way to handle time-outs is to use Erlang Timers; see │ │ │ erlang:start_timer/3,4. Most time-out tasks │ │ │ can be performed with the time-out features in gen_statem, │ │ │ but an example of one that cannot is if you should need the return value │ │ │ from erlang:cancel_timer(Tref), that is, │ │ │ the remaining time of the timer.

        Here is how to accomplish the state time-out in the previous example │ │ │ by instead using an Erlang Timer:

        ...
        │ │ │ -locked(
        │ │ │ -  cast, {button,Button},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +locked(
        │ │ │ +  cast, {button,Button},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │  ...
        │ │ │      if
        │ │ │          NewButtons =:= Code -> % Correct
        │ │ │ -	    do_unlock(),
        │ │ │ +	    do_unlock(),
        │ │ │  	    Tref =
        │ │ │ -                 erlang:start_timer(
        │ │ │ -                     10_000, self(), lock), % Time in milliseconds
        │ │ │ -            {next_state, open, Data#{buttons := [], timer => Tref}};
        │ │ │ +                 erlang:start_timer(
        │ │ │ +                     10_000, self(), lock), % Time in milliseconds
        │ │ │ +            {next_state, open, Data#{buttons := [], timer => Tref}};
        │ │ │  ...
        │ │ │  
        │ │ │ -open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {next_state,locked,maps:remove(timer, Data)};
        │ │ │ -open(cast, {button,_}, Data) ->
        │ │ │ -    {keep_state,Data};
        │ │ │ +open(info, {timeout,Tref,lock}, #{timer := Tref} = Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {next_state,locked,maps:remove(timer, Data)};
        │ │ │ +open(cast, {button,_}, Data) ->
        │ │ │ +    {keep_state,Data};
        │ │ │  ...

        Removing the timer key from the map when we do a state change to locked │ │ │ is not strictly necessary since we can only get into state open │ │ │ with an updated timer map value. But it can be nice to not have │ │ │ outdated values in the state Data.

        If you need to cancel a timer because of some other event, you can use │ │ │ erlang:cancel_timer(Tref). Note that no time-out │ │ │ message will arrive after this (because the timer has been │ │ │ explicitly canceled), unless you have already postponed one earlier │ │ │ @@ -910,16 +910,16 @@ │ │ │ Postponing Events │ │ │

        │ │ │

        If you want to ignore a particular event in the current state and handle it │ │ │ in a future state, you can postpone the event. A postponed event │ │ │ is retried after a state change, that is, OldState =/= NewState.

        Postponing is ordered by the │ │ │ transition action postpone.

        In this example, instead of ignoring button events while in the open state, │ │ │ we can postpone them handle them later in the locked state:

        ...
        │ │ │ -open(cast, {button,_}, Data) ->
        │ │ │ -    {keep_state,Data,[postpone]};
        │ │ │ +open(cast, {button,_}, Data) ->
        │ │ │ +    {keep_state,Data,[postpone]};
        │ │ │  ...

        Since a postponed event is only retried after a state change, you have to │ │ │ think about where to keep a state data item. You can keep it in the server │ │ │ Data or in the State itself, for example by having two more or less │ │ │ identical states to keep a boolean value, or by using a complex state (see │ │ │ section Complex State) with │ │ │ callback mode │ │ │ handle_event_function. If a change │ │ │ @@ -940,55 +940,55 @@ │ │ │ │ │ │ │ │ │ │ │ │ Selective Receive │ │ │ │ │ │

        Erlang's selective receive statement is often used to describe simple state │ │ │ machine examples in straightforward Erlang code. The following is a possible │ │ │ -implementation of the first example:

        -module(code_lock).
        │ │ │ --define(NAME, code_lock_1).
        │ │ │ --export([start_link/1,button/1]).
        │ │ │ -
        │ │ │ -start_link(Code) ->
        │ │ │ -    spawn(
        │ │ │ -      fun () ->
        │ │ │ -	      true = register(?NAME, self()),
        │ │ │ -	      do_lock(),
        │ │ │ -	      locked(Code, length(Code), [])
        │ │ │ -      end).
        │ │ │ +implementation of the first example:

        -module(code_lock).
        │ │ │ +-define(NAME, code_lock_1).
        │ │ │ +-export([start_link/1,button/1]).
        │ │ │ +
        │ │ │ +start_link(Code) ->
        │ │ │ +    spawn(
        │ │ │ +      fun () ->
        │ │ │ +	      true = register(?NAME, self()),
        │ │ │ +	      do_lock(),
        │ │ │ +	      locked(Code, length(Code), [])
        │ │ │ +      end).
        │ │ │  
        │ │ │ -button(Button) ->
        │ │ │ -    ?NAME ! {button,Button}.
        locked(Code, Length, Buttons) ->
        │ │ │ +button(Button) ->
        │ │ │ +    ?NAME ! {button,Button}.
        locked(Code, Length, Buttons) ->
        │ │ │      receive
        │ │ │ -        {button,Button} ->
        │ │ │ +        {button,Button} ->
        │ │ │              NewButtons =
        │ │ │                  if
        │ │ │ -                    length(Buttons) < Length ->
        │ │ │ +                    length(Buttons) < Length ->
        │ │ │                          Buttons;
        │ │ │                      true ->
        │ │ │ -                        tl(Buttons)
        │ │ │ -                end ++ [Button],
        │ │ │ +                        tl(Buttons)
        │ │ │ +                end ++ [Button],
        │ │ │              if
        │ │ │                  NewButtons =:= Code -> % Correct
        │ │ │ -                    do_unlock(),
        │ │ │ -		    open(Code, Length);
        │ │ │ +                    do_unlock(),
        │ │ │ +		    open(Code, Length);
        │ │ │                  true -> % Incomplete | Incorrect
        │ │ │ -                    locked(Code, Length, NewButtons)
        │ │ │ +                    locked(Code, Length, NewButtons)
        │ │ │              end
        │ │ │ -    end.
        open(Code, Length) ->
        │ │ │ +    end.
        open(Code, Length) ->
        │ │ │      receive
        │ │ │      after 10_000 -> % Time in milliseconds
        │ │ │ -	    do_lock(),
        │ │ │ -	    locked(Code, Length, [])
        │ │ │ +	    do_lock(),
        │ │ │ +	    locked(Code, Length, [])
        │ │ │      end.
        │ │ │  
        │ │ │ -do_lock() ->
        │ │ │ -    io:format("Locked~n", []).
        │ │ │ -do_unlock() ->
        │ │ │ -    io:format("Open~n", []).

        The selective receive in this case causes open to implicitly postpone any │ │ │ +do_lock() -> │ │ │ + io:format("Locked~n", []). │ │ │ +do_unlock() -> │ │ │ + io:format("Open~n", []).

        The selective receive in this case causes open to implicitly postpone any │ │ │ events to the locked state.

        A catch-all receive should never be used from a gen_statem behaviour │ │ │ (or from any gen_* behaviour), as the receive statement is within │ │ │ the gen_* engine itself. sys-compatible behaviours must respond to │ │ │ system messages and therefore do that in their engine receive loop, │ │ │ passing non-system messages to the callback module. Using a catch-all │ │ │ receive can result in system messages being discarded, which in turn │ │ │ can lead to unexpected behaviour. If a selective receive must be used, │ │ │ @@ -1011,40 +1011,40 @@ │ │ │ section), especially if only one or a few states have state enter actions, │ │ │ this is a perfect use case for the built in │ │ │ state enter calls.

        You return a list containing state_enter from your │ │ │ callback_mode/0 function and the │ │ │ gen_statem engine will call your state callback once with an event │ │ │ (enter, OldState, ...) whenever it does a state change. Then you │ │ │ just need to handle these event-like calls in all states.

        ...
        │ │ │ -init(Code) ->
        │ │ │ -    process_flag(trap_exit, true),
        │ │ │ -    Data = #{code => Code, length = length(Code)},
        │ │ │ -    {ok, locked, Data}.
        │ │ │ -
        │ │ │ -callback_mode() ->
        │ │ │ -    [state_functions,state_enter].
        │ │ │ -
        │ │ │ -locked(enter, _OldState, Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {keep_state,Data#{buttons => []}};
        │ │ │ -locked(
        │ │ │ -  cast, {button,Button},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +init(Code) ->
        │ │ │ +    process_flag(trap_exit, true),
        │ │ │ +    Data = #{code => Code, length = length(Code)},
        │ │ │ +    {ok, locked, Data}.
        │ │ │ +
        │ │ │ +callback_mode() ->
        │ │ │ +    [state_functions,state_enter].
        │ │ │ +
        │ │ │ +locked(enter, _OldState, Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {keep_state,Data#{buttons => []}};
        │ │ │ +locked(
        │ │ │ +  cast, {button,Button},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │  ...
        │ │ │      if
        │ │ │          NewButtons =:= Code -> % Correct
        │ │ │ -            {next_state, open, Data};
        │ │ │ +            {next_state, open, Data};
        │ │ │  ...
        │ │ │  
        │ │ │ -open(enter, _OldState, _Data) ->
        │ │ │ -    do_unlock(),
        │ │ │ -    {keep_state_and_data,
        │ │ │ -     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ -open(state_timeout, lock, Data) ->
        │ │ │ -    {next_state, locked, Data};
        │ │ │ +open(enter, _OldState, _Data) ->
        │ │ │ +    do_unlock(),
        │ │ │ +    {keep_state_and_data,
        │ │ │ +     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ +open(state_timeout, lock, Data) ->
        │ │ │ +    {next_state, locked, Data};
        │ │ │  ...

        You can repeat the state enter code by returning one of │ │ │ {repeat_state, ...},{repeat_state_and_data, _}, │ │ │ or repeat_state_and_data that otherwise behaves exactly like their │ │ │ keep_state siblings. See the type │ │ │ state_callback_result() │ │ │ in the Reference Manual.

        │ │ │ │ │ │ @@ -1066,44 +1066,44 @@ │ │ │ to dispatch pre-processed events as internal events to the main state │ │ │ machine.

        Using internal events also can make it easier to synchronize the state │ │ │ machines.

        A variant of this is to use a complex state with │ │ │ one state callback, modeling the state │ │ │ with, for example, a tuple {MainFSMState, SubFSMState}.

        To illustrate this we make up an example where the buttons instead generate │ │ │ down and up (press and release) events, and the lock responds │ │ │ to an up event only after the corresponding down event.

        ...
        │ │ │ --export([down/1, up/1]).
        │ │ │ +-export([down/1, up/1]).
        │ │ │  ...
        │ │ │ -down(Button) ->
        │ │ │ -    gen_statem:cast(?NAME, {down,Button}).
        │ │ │ +down(Button) ->
        │ │ │ +    gen_statem:cast(?NAME, {down,Button}).
        │ │ │  
        │ │ │ -up(Button) ->
        │ │ │ -    gen_statem:cast(?NAME, {up,Button}).
        │ │ │ +up(Button) ->
        │ │ │ +    gen_statem:cast(?NAME, {up,Button}).
        │ │ │  
        │ │ │  ...
        │ │ │  
        │ │ │ -locked(enter, _OldState, Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {keep_state,Data#{buttons => []}};
        │ │ │ -locked(
        │ │ │ -  internal, {button,Button},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ -...
        handle_common(cast, {down,Button}, Data) ->
        │ │ │ -    {keep_state, Data#{button => Button}};
        │ │ │ -handle_common(cast, {up,Button}, Data) ->
        │ │ │ +locked(enter, _OldState, Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {keep_state,Data#{buttons => []}};
        │ │ │ +locked(
        │ │ │ +  internal, {button,Button},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +...
        handle_common(cast, {down,Button}, Data) ->
        │ │ │ +    {keep_state, Data#{button => Button}};
        │ │ │ +handle_common(cast, {up,Button}, Data) ->
        │ │ │      case Data of
        │ │ │ -        #{button := Button} ->
        │ │ │ -            {keep_state,maps:remove(button, Data),
        │ │ │ -             [{next_event,internal,{button,Button}}]};
        │ │ │ -        #{} ->
        │ │ │ +        #{button := Button} ->
        │ │ │ +            {keep_state,maps:remove(button, Data),
        │ │ │ +             [{next_event,internal,{button,Button}}]};
        │ │ │ +        #{} ->
        │ │ │              keep_state_and_data
        │ │ │      end;
        │ │ │  ...
        │ │ │  
        │ │ │ -open(internal, {button,_}, Data) ->
        │ │ │ -    {keep_state,Data,[postpone]};
        │ │ │ +open(internal, {button,_}, Data) ->
        │ │ │ +    {keep_state,Data,[postpone]};
        │ │ │  ...

        If you start this program with code_lock:start([17]) you can unlock with │ │ │ code_lock:down(17), code_lock:up(17).

        │ │ │ │ │ │ │ │ │ │ │ │ Example Revisited │ │ │

        │ │ │ @@ -1131,152 +1131,152 @@ │ │ │ Also, the state diagram does not show that the code_length/0 call │ │ │ must be handled in every state.

        │ │ │ │ │ │ │ │ │ │ │ │ Callback Mode: state_functions │ │ │

        │ │ │ -

        Using state functions:

        -module(code_lock).
        │ │ │ --behaviour(gen_statem).
        │ │ │ --define(NAME, code_lock_2).
        │ │ │ +

        Using state functions:

        -module(code_lock).
        │ │ │ +-behaviour(gen_statem).
        │ │ │ +-define(NAME, code_lock_2).
        │ │ │  
        │ │ │ --export([start_link/1,stop/0]).
        │ │ │ --export([down/1,up/1,code_length/0]).
        │ │ │ --export([init/1,callback_mode/0,terminate/3]).
        │ │ │ --export([locked/3,open/3]).
        │ │ │ -
        │ │ │ -start_link(Code) ->
        │ │ │ -    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
        │ │ │ -stop() ->
        │ │ │ -    gen_statem:stop(?NAME).
        │ │ │ -
        │ │ │ -down(Button) ->
        │ │ │ -    gen_statem:cast(?NAME, {down,Button}).
        │ │ │ -up(Button) ->
        │ │ │ -    gen_statem:cast(?NAME, {up,Button}).
        │ │ │ -code_length() ->
        │ │ │ -    gen_statem:call(?NAME, code_length).
        init(Code) ->
        │ │ │ -    process_flag(trap_exit, true),
        │ │ │ -    Data = #{code => Code, length => length(Code), buttons => []},
        │ │ │ -    {ok, locked, Data}.
        │ │ │ +-export([start_link/1,stop/0]).
        │ │ │ +-export([down/1,up/1,code_length/0]).
        │ │ │ +-export([init/1,callback_mode/0,terminate/3]).
        │ │ │ +-export([locked/3,open/3]).
        │ │ │ +
        │ │ │ +start_link(Code) ->
        │ │ │ +    gen_statem:start_link({local,?NAME}, ?MODULE, Code, []).
        │ │ │ +stop() ->
        │ │ │ +    gen_statem:stop(?NAME).
        │ │ │ +
        │ │ │ +down(Button) ->
        │ │ │ +    gen_statem:cast(?NAME, {down,Button}).
        │ │ │ +up(Button) ->
        │ │ │ +    gen_statem:cast(?NAME, {up,Button}).
        │ │ │ +code_length() ->
        │ │ │ +    gen_statem:call(?NAME, code_length).
        init(Code) ->
        │ │ │ +    process_flag(trap_exit, true),
        │ │ │ +    Data = #{code => Code, length => length(Code), buttons => []},
        │ │ │ +    {ok, locked, Data}.
        │ │ │  
        │ │ │ -callback_mode() ->
        │ │ │ -    [state_functions,state_enter].
        │ │ │ +callback_mode() ->
        │ │ │ +    [state_functions,state_enter].
        │ │ │  
        │ │ │ --define(HANDLE_COMMON,
        │ │ │ -    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, D)).
        │ │ │ +-define(HANDLE_COMMON,
        │ │ │ +    ?FUNCTION_NAME(T, C, D) -> handle_common(T, C, D)).
        │ │ │  %%
        │ │ │ -handle_common(cast, {down,Button}, Data) ->
        │ │ │ -    {keep_state, Data#{button => Button}};
        │ │ │ -handle_common(cast, {up,Button}, Data) ->
        │ │ │ +handle_common(cast, {down,Button}, Data) ->
        │ │ │ +    {keep_state, Data#{button => Button}};
        │ │ │ +handle_common(cast, {up,Button}, Data) ->
        │ │ │      case Data of
        │ │ │ -        #{button := Button} ->
        │ │ │ -            {keep_state, maps:remove(button, Data),
        │ │ │ -             [{next_event,internal,{button,Button}}]};
        │ │ │ -        #{} ->
        │ │ │ +        #{button := Button} ->
        │ │ │ +            {keep_state, maps:remove(button, Data),
        │ │ │ +             [{next_event,internal,{button,Button}}]};
        │ │ │ +        #{} ->
        │ │ │              keep_state_and_data
        │ │ │      end;
        │ │ │ -handle_common({call,From}, code_length, #{code := Code}) ->
        │ │ │ -    {keep_state_and_data,
        │ │ │ -     [{reply,From,length(Code)}]}.
        locked(enter, _OldState, Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {keep_state, Data#{buttons := []}};
        │ │ │ -locked(state_timeout, button, Data) ->
        │ │ │ -    {keep_state, Data#{buttons := []}};
        │ │ │ -locked(
        │ │ │ -  internal, {button,Button},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +handle_common({call,From}, code_length, #{code := Code}) ->
        │ │ │ +    {keep_state_and_data,
        │ │ │ +     [{reply,From,length(Code)}]}.
        locked(enter, _OldState, Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {keep_state, Data#{buttons := []}};
        │ │ │ +locked(state_timeout, button, Data) ->
        │ │ │ +    {keep_state, Data#{buttons := []}};
        │ │ │ +locked(
        │ │ │ +  internal, {button,Button},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │      NewButtons =
        │ │ │          if
        │ │ │ -            length(Buttons) < Length ->
        │ │ │ +            length(Buttons) < Length ->
        │ │ │                  Buttons;
        │ │ │              true ->
        │ │ │ -                tl(Buttons)
        │ │ │ -        end ++ [Button],
        │ │ │ +                tl(Buttons)
        │ │ │ +        end ++ [Button],
        │ │ │      if
        │ │ │          NewButtons =:= Code -> % Correct
        │ │ │ -            {next_state, open, Data};
        │ │ │ +            {next_state, open, Data};
        │ │ │  	true -> % Incomplete | Incorrect
        │ │ │ -            {keep_state, Data#{buttons := NewButtons},
        │ │ │ -             [{state_timeout,30_000,button}]} % Time in milliseconds
        │ │ │ +            {keep_state, Data#{buttons := NewButtons},
        │ │ │ +             [{state_timeout,30_000,button}]} % Time in milliseconds
        │ │ │      end;
        │ │ │ -?HANDLE_COMMON.
        open(enter, _OldState, _Data) ->
        │ │ │ -    do_unlock(),
        │ │ │ -    {keep_state_and_data,
        │ │ │ -     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ -open(state_timeout, lock, Data) ->
        │ │ │ -    {next_state, locked, Data};
        │ │ │ -open(internal, {button,_}, _) ->
        │ │ │ -    {keep_state_and_data, [postpone]};
        │ │ │ +?HANDLE_COMMON.
        open(enter, _OldState, _Data) ->
        │ │ │ +    do_unlock(),
        │ │ │ +    {keep_state_and_data,
        │ │ │ +     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ +open(state_timeout, lock, Data) ->
        │ │ │ +    {next_state, locked, Data};
        │ │ │ +open(internal, {button,_}, _) ->
        │ │ │ +    {keep_state_and_data, [postpone]};
        │ │ │  ?HANDLE_COMMON.
        │ │ │  
        │ │ │ -do_lock() ->
        │ │ │ -    io:format("Locked~n", []).
        │ │ │ -do_unlock() ->
        │ │ │ -    io:format("Open~n", []).
        │ │ │ +do_lock() ->
        │ │ │ +    io:format("Locked~n", []).
        │ │ │ +do_unlock() ->
        │ │ │ +    io:format("Open~n", []).
        │ │ │  
        │ │ │ -terminate(_Reason, State, _Data) ->
        │ │ │ -    State =/= locked andalso do_lock(),
        │ │ │ +terminate(_Reason, State, _Data) ->
        │ │ │ +    State =/= locked andalso do_lock(),
        │ │ │      ok.

        │ │ │ │ │ │ │ │ │ │ │ │ Callback Mode: handle_event_function │ │ │

        │ │ │

        This section describes what to change in the example to use one │ │ │ handle_event/4 function. The previously used approach to first branch │ │ │ depending on event does not work that well here because of │ │ │ -the state enter calls, so this example first branches depending on state:

        -export([handle_event/4]).
        callback_mode() ->
        │ │ │ -    [handle_event_function,state_enter].
        %%
        │ │ │ +the state enter calls, so this example first branches depending on state:

        -export([handle_event/4]).
        callback_mode() ->
        │ │ │ +    [handle_event_function,state_enter].
        %%
        │ │ │  %% State: locked
        │ │ │ -handle_event(enter, _OldState, locked, Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {keep_state, Data#{buttons := []}};
        │ │ │ -handle_event(state_timeout, button, locked, Data) ->
        │ │ │ -    {keep_state, Data#{buttons := []}};
        │ │ │ -handle_event(
        │ │ │ -  internal, {button,Button}, locked,
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +handle_event(enter, _OldState, locked, Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {keep_state, Data#{buttons := []}};
        │ │ │ +handle_event(state_timeout, button, locked, Data) ->
        │ │ │ +    {keep_state, Data#{buttons := []}};
        │ │ │ +handle_event(
        │ │ │ +  internal, {button,Button}, locked,
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │      NewButtons =
        │ │ │          if
        │ │ │ -            length(Buttons) < Length ->
        │ │ │ +            length(Buttons) < Length ->
        │ │ │                  Buttons;
        │ │ │              true ->
        │ │ │ -                tl(Buttons)
        │ │ │ -        end ++ [Button],
        │ │ │ +                tl(Buttons)
        │ │ │ +        end ++ [Button],
        │ │ │      if
        │ │ │          NewButtons =:= Code -> % Correct
        │ │ │ -            {next_state, open, Data};
        │ │ │ +            {next_state, open, Data};
        │ │ │  	true -> % Incomplete | Incorrect
        │ │ │ -            {keep_state, Data#{buttons := NewButtons},
        │ │ │ -             [{state_timeout,30_000,button}]} % Time in milliseconds
        │ │ │ +            {keep_state, Data#{buttons := NewButtons},
        │ │ │ +             [{state_timeout,30_000,button}]} % Time in milliseconds
        │ │ │      end;
        %%
        │ │ │  %% State: open
        │ │ │ -handle_event(enter, _OldState, open, _Data) ->
        │ │ │ -    do_unlock(),
        │ │ │ -    {keep_state_and_data,
        │ │ │ -     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ -handle_event(state_timeout, lock, open, Data) ->
        │ │ │ -    {next_state, locked, Data};
        │ │ │ -handle_event(internal, {button,_}, open, _) ->
        │ │ │ -    {keep_state_and_data,[postpone]};
        %% Common events
        │ │ │ -handle_event(cast, {down,Button}, _State, Data) ->
        │ │ │ -    {keep_state, Data#{button => Button}};
        │ │ │ -handle_event(cast, {up,Button}, _State, Data) ->
        │ │ │ +handle_event(enter, _OldState, open, _Data) ->
        │ │ │ +    do_unlock(),
        │ │ │ +    {keep_state_and_data,
        │ │ │ +     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ +handle_event(state_timeout, lock, open, Data) ->
        │ │ │ +    {next_state, locked, Data};
        │ │ │ +handle_event(internal, {button,_}, open, _) ->
        │ │ │ +    {keep_state_and_data,[postpone]};
        %% Common events
        │ │ │ +handle_event(cast, {down,Button}, _State, Data) ->
        │ │ │ +    {keep_state, Data#{button => Button}};
        │ │ │ +handle_event(cast, {up,Button}, _State, Data) ->
        │ │ │      case Data of
        │ │ │ -        #{button := Button} ->
        │ │ │ -            {keep_state, maps:remove(button, Data),
        │ │ │ -             [{next_event,internal,{button,Button}},
        │ │ │ -              {state_timeout,30_000,button}]}; % Time in milliseconds
        │ │ │ -        #{} ->
        │ │ │ +        #{button := Button} ->
        │ │ │ +            {keep_state, maps:remove(button, Data),
        │ │ │ +             [{next_event,internal,{button,Button}},
        │ │ │ +              {state_timeout,30_000,button}]}; % Time in milliseconds
        │ │ │ +        #{} ->
        │ │ │              keep_state_and_data
        │ │ │      end;
        │ │ │ -handle_event({call,From}, code_length, _State, #{length := Length}) ->
        │ │ │ -    {keep_state_and_data,
        │ │ │ -     [{reply,From,Length}]}.

        Notice that postponing buttons from the open state to the locked state │ │ │ +handle_event({call,From}, code_length, _State, #{length := Length}) -> │ │ │ + {keep_state_and_data, │ │ │ + [{reply,From,Length}]}.

        Notice that postponing buttons from the open state to the locked state │ │ │ seems like a strange thing to do for a code lock, but it at least │ │ │ illustrates event postponing.

        │ │ │ │ │ │ │ │ │ │ │ │ Filter the State │ │ │

        │ │ │ @@ -1286,30 +1286,30 @@ │ │ │ and which digits that remain to unlock.

        This state data can be regarded as sensitive, and maybe not what you want │ │ │ in the error log because of some unpredictable event.

        Another reason to filter the state can be that the state is too large to print, │ │ │ as it fills the error log with uninteresting details.

        To avoid this, you can format the internal state that gets in the error log │ │ │ and gets returned from sys:get_status/1,2 │ │ │ by implementing function │ │ │ Module:format_status/2, │ │ │ for example like this:

        ...
        │ │ │ --export([init/1,terminate/3,format_status/2]).
        │ │ │ +-export([init/1,terminate/3,format_status/2]).
        │ │ │  ...
        │ │ │  
        │ │ │ -format_status(Opt, [_PDict,State,Data]) ->
        │ │ │ +format_status(Opt, [_PDict,State,Data]) ->
        │ │ │      StateData =
        │ │ │ -	{State,
        │ │ │ -	 maps:filter(
        │ │ │ -	   fun (code, _) -> false;
        │ │ │ -	       (_, _) -> true
        │ │ │ +	{State,
        │ │ │ +	 maps:filter(
        │ │ │ +	   fun (code, _) -> false;
        │ │ │ +	       (_, _) -> true
        │ │ │  	   end,
        │ │ │ -	   Data)},
        │ │ │ +	   Data)},
        │ │ │      case Opt of
        │ │ │  	terminate ->
        │ │ │  	    StateData;
        │ │ │  	normal ->
        │ │ │ -	    [{data,[{"State",StateData}]}]
        │ │ │ +	    [{data,[{"State",StateData}]}]
        │ │ │      end.

        It is not mandatory to implement a │ │ │ Module:format_status/2 function. │ │ │ If you do not, a default implementation is used that does the same │ │ │ as this example function without filtering the Data term, that is, │ │ │ StateData = {State, Data}, in this example containing sensitive information.

        │ │ │ │ │ │ │ │ │ @@ -1322,104 +1322,104 @@ │ │ │ like a tuple.

        One reason to use this is when you have a state item that when changed │ │ │ should cancel the state time-out, or one that affects │ │ │ the event handling in combination with postponing events. We will go for │ │ │ the latter and complicate the previous example by introducing │ │ │ a configurable lock button (this is the state item in question), │ │ │ which in the open state immediately locks the door, and an API function │ │ │ set_lock_button/1 to set the lock button.

        Suppose now that we call set_lock_button while the door is open, │ │ │ -and we have already postponed a button event that was the new lock button:

        1> code_lock:start_link([a,b,c], x).
        │ │ │ -{ok,<0.666.0>}
        │ │ │ -2> code_lock:button(a).
        │ │ │ +and we have already postponed a button event that was the new lock button:

        1> code_lock:start_link([a,b,c], x).
        │ │ │ +{ok,<0.666.0>}
        │ │ │ +2> code_lock:button(a).
        │ │ │  ok
        │ │ │ -3> code_lock:button(b).
        │ │ │ +3> code_lock:button(b).
        │ │ │  ok
        │ │ │ -4> code_lock:button(c).
        │ │ │ +4> code_lock:button(c).
        │ │ │  ok
        │ │ │  Open
        │ │ │ -5> code_lock:button(y).
        │ │ │ +5> code_lock:button(y).
        │ │ │  ok
        │ │ │ -6> code_lock:set_lock_button(y).
        │ │ │ +6> code_lock:set_lock_button(y).
        │ │ │  x
        │ │ │  % What should happen here?  Immediate lock or nothing?

        We could say that the button was pressed too early so it should not be │ │ │ recognized as the lock button. Or we can make the lock button part of │ │ │ the state so when we then change the lock button in the locked state, │ │ │ the change becomes a state change and all postponed events are retried, │ │ │ therefore the lock is immediately locked!

        We define the state as {StateName, LockButton}, where StateName │ │ │ -is as before and LockButton is the current lock button:

        -module(code_lock).
        │ │ │ --behaviour(gen_statem).
        │ │ │ --define(NAME, code_lock_3).
        │ │ │ +is as before and LockButton is the current lock button:

        -module(code_lock).
        │ │ │ +-behaviour(gen_statem).
        │ │ │ +-define(NAME, code_lock_3).
        │ │ │  
        │ │ │ --export([start_link/2,stop/0]).
        │ │ │ --export([button/1,set_lock_button/1]).
        │ │ │ --export([init/1,callback_mode/0,terminate/3]).
        │ │ │ --export([handle_event/4]).
        │ │ │ -
        │ │ │ -start_link(Code, LockButton) ->
        │ │ │ -    gen_statem:start_link(
        │ │ │ -        {local,?NAME}, ?MODULE, {Code,LockButton}, []).
        │ │ │ -stop() ->
        │ │ │ -    gen_statem:stop(?NAME).
        │ │ │ -
        │ │ │ -button(Button) ->
        │ │ │ -    gen_statem:cast(?NAME, {button,Button}).
        │ │ │ -set_lock_button(LockButton) ->
        │ │ │ -    gen_statem:call(?NAME, {set_lock_button,LockButton}).
        init({Code,LockButton}) ->
        │ │ │ -    process_flag(trap_exit, true),
        │ │ │ -    Data = #{code => Code, length => length(Code), buttons => []},
        │ │ │ -    {ok, {locked,LockButton}, Data}.
        │ │ │ +-export([start_link/2,stop/0]).
        │ │ │ +-export([button/1,set_lock_button/1]).
        │ │ │ +-export([init/1,callback_mode/0,terminate/3]).
        │ │ │ +-export([handle_event/4]).
        │ │ │ +
        │ │ │ +start_link(Code, LockButton) ->
        │ │ │ +    gen_statem:start_link(
        │ │ │ +        {local,?NAME}, ?MODULE, {Code,LockButton}, []).
        │ │ │ +stop() ->
        │ │ │ +    gen_statem:stop(?NAME).
        │ │ │ +
        │ │ │ +button(Button) ->
        │ │ │ +    gen_statem:cast(?NAME, {button,Button}).
        │ │ │ +set_lock_button(LockButton) ->
        │ │ │ +    gen_statem:call(?NAME, {set_lock_button,LockButton}).
        init({Code,LockButton}) ->
        │ │ │ +    process_flag(trap_exit, true),
        │ │ │ +    Data = #{code => Code, length => length(Code), buttons => []},
        │ │ │ +    {ok, {locked,LockButton}, Data}.
        │ │ │  
        │ │ │ -callback_mode() ->
        │ │ │ -    [handle_event_function,state_enter].
        │ │ │ +callback_mode() ->
        │ │ │ +    [handle_event_function,state_enter].
        │ │ │  
        │ │ │  %% State: locked
        │ │ │ -handle_event(enter, _OldState, {locked,_}, Data) ->
        │ │ │ -    do_lock(),
        │ │ │ -    {keep_state, Data#{buttons := []}};
        │ │ │ -handle_event(state_timeout, button, {locked,_}, Data) ->
        │ │ │ -    {keep_state, Data#{buttons := []}};
        │ │ │ -handle_event(
        │ │ │ -  cast, {button,Button}, {locked,LockButton},
        │ │ │ -  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │ +handle_event(enter, _OldState, {locked,_}, Data) ->
        │ │ │ +    do_lock(),
        │ │ │ +    {keep_state, Data#{buttons := []}};
        │ │ │ +handle_event(state_timeout, button, {locked,_}, Data) ->
        │ │ │ +    {keep_state, Data#{buttons := []}};
        │ │ │ +handle_event(
        │ │ │ +  cast, {button,Button}, {locked,LockButton},
        │ │ │ +  #{code := Code, length := Length, buttons := Buttons} = Data) ->
        │ │ │      NewButtons =
        │ │ │          if
        │ │ │ -            length(Buttons) < Length ->
        │ │ │ +            length(Buttons) < Length ->
        │ │ │                  Buttons;
        │ │ │              true ->
        │ │ │ -                tl(Buttons)
        │ │ │ -        end ++ [Button],
        │ │ │ +                tl(Buttons)
        │ │ │ +        end ++ [Button],
        │ │ │      if
        │ │ │          NewButtons =:= Code -> % Correct
        │ │ │ -            {next_state, {open,LockButton}, Data};
        │ │ │ +            {next_state, {open,LockButton}, Data};
        │ │ │  	true -> % Incomplete | Incorrect
        │ │ │ -            {keep_state, Data#{buttons := NewButtons},
        │ │ │ -             [{state_timeout,30_000,button}]} % Time in milliseconds
        │ │ │ +            {keep_state, Data#{buttons := NewButtons},
        │ │ │ +             [{state_timeout,30_000,button}]} % Time in milliseconds
        │ │ │      end;
        %%
        │ │ │  %% State: open
        │ │ │ -handle_event(enter, _OldState, {open,_}, _Data) ->
        │ │ │ -    do_unlock(),
        │ │ │ -    {keep_state_and_data,
        │ │ │ -     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ -handle_event(state_timeout, lock, {open,LockButton}, Data) ->
        │ │ │ -    {next_state, {locked,LockButton}, Data};
        │ │ │ -handle_event(cast, {button,LockButton}, {open,LockButton}, Data) ->
        │ │ │ -    {next_state, {locked,LockButton}, Data};
        │ │ │ -handle_event(cast, {button,_}, {open,_}, _Data) ->
        │ │ │ -    {keep_state_and_data,[postpone]};
        %%
        │ │ │ +handle_event(enter, _OldState, {open,_}, _Data) ->
        │ │ │ +    do_unlock(),
        │ │ │ +    {keep_state_and_data,
        │ │ │ +     [{state_timeout,10_000,lock}]}; % Time in milliseconds
        │ │ │ +handle_event(state_timeout, lock, {open,LockButton}, Data) ->
        │ │ │ +    {next_state, {locked,LockButton}, Data};
        │ │ │ +handle_event(cast, {button,LockButton}, {open,LockButton}, Data) ->
        │ │ │ +    {next_state, {locked,LockButton}, Data};
        │ │ │ +handle_event(cast, {button,_}, {open,_}, _Data) ->
        │ │ │ +    {keep_state_and_data,[postpone]};
        %%
        │ │ │  %% Common events
        │ │ │ -handle_event(
        │ │ │ -  {call,From}, {set_lock_button,NewLockButton},
        │ │ │ -  {StateName,OldLockButton}, Data) ->
        │ │ │ -    {next_state, {StateName,NewLockButton}, Data,
        │ │ │ -     [{reply,From,OldLockButton}]}.
        do_lock() ->
        │ │ │ -    io:format("Locked~n", []).
        │ │ │ -do_unlock() ->
        │ │ │ -    io:format("Open~n", []).
        │ │ │ +handle_event(
        │ │ │ +  {call,From}, {set_lock_button,NewLockButton},
        │ │ │ +  {StateName,OldLockButton}, Data) ->
        │ │ │ +    {next_state, {StateName,NewLockButton}, Data,
        │ │ │ +     [{reply,From,OldLockButton}]}.
        do_lock() ->
        │ │ │ +    io:format("Locked~n", []).
        │ │ │ +do_unlock() ->
        │ │ │ +    io:format("Open~n", []).
        │ │ │  
        │ │ │ -terminate(_Reason, State, _Data) ->
        │ │ │ -    State =/= locked andalso do_lock(),
        │ │ │ +terminate(_Reason, State, _Data) ->
        │ │ │ +    State =/= locked andalso do_lock(),
        │ │ │      ok.

        │ │ │ │ │ │ │ │ │ │ │ │ Hibernation │ │ │

        │ │ │

        If you have many servers in one node and they have some state(s) in their │ │ │ @@ -1428,19 +1428,19 @@ │ │ │ footprint of a server can be minimized by hibernating it through │ │ │ proc_lib:hibernate/3.

        Note

        It is rather costly to hibernate a process; see erlang:hibernate/3. It is │ │ │ not something you want to do after every event.

        We can in this example hibernate in the {open, _} state, │ │ │ because what normally occurs in that state is that the state time-out │ │ │ after a while triggers a transition to {locked, _}:

        ...
        │ │ │  %%
        │ │ │  %% State: open
        │ │ │ -handle_event(enter, _OldState, {open,_}, _Data) ->
        │ │ │ -    do_unlock(),
        │ │ │ -    {keep_state_and_data,
        │ │ │ -     [{state_timeout,10_000,lock}, % Time in milliseconds
        │ │ │ -      hibernate]};
        │ │ │ +handle_event(enter, _OldState, {open,_}, _Data) ->
        │ │ │ +    do_unlock(),
        │ │ │ +    {keep_state_and_data,
        │ │ │ +     [{state_timeout,10_000,lock}, % Time in milliseconds
        │ │ │ +      hibernate]};
        │ │ │  ...

        The atom hibernate in the action list on the │ │ │ last line when entering the {open, _} state is the only change. If any event │ │ │ arrives in the {open, _}, state, we do not bother to rehibernate, │ │ │ so the server stays awake after any event.

        To change that we would need to insert action hibernate in more places. │ │ │ For example, the state-independent set_lock_button operation │ │ │ would have to use hibernate but only in the {open, _} state, │ │ │ which would clutter the code.

        Another not uncommon scenario is to use the │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/sup_princ.html │ │ │ @@ -128,48 +128,48 @@ │ │ │ the order specified by this list, and are terminated in the reverse order.

        │ │ │ │ │ │ │ │ │ │ │ │ Example │ │ │

        │ │ │

        The callback module for a supervisor starting the server from │ │ │ -gen_server Behaviour can look as follows:

        -module(ch_sup).
        │ │ │ --behaviour(supervisor).
        │ │ │ +gen_server Behaviour can look as follows:

        -module(ch_sup).
        │ │ │ +-behaviour(supervisor).
        │ │ │  
        │ │ │ --export([start_link/0]).
        │ │ │ --export([init/1]).
        │ │ │ +-export([start_link/0]).
        │ │ │ +-export([init/1]).
        │ │ │  
        │ │ │ -start_link() ->
        │ │ │ -    supervisor:start_link(ch_sup, []).
        │ │ │ +start_link() ->
        │ │ │ +    supervisor:start_link(ch_sup, []).
        │ │ │  
        │ │ │ -init(_Args) ->
        │ │ │ -    SupFlags = #{strategy => one_for_one, intensity => 1, period => 5},
        │ │ │ -    ChildSpecs = [#{id => ch3,
        │ │ │ -                    start => {ch3, start_link, []},
        │ │ │ +init(_Args) ->
        │ │ │ +    SupFlags = #{strategy => one_for_one, intensity => 1, period => 5},
        │ │ │ +    ChildSpecs = [#{id => ch3,
        │ │ │ +                    start => {ch3, start_link, []},
        │ │ │                      restart => permanent,
        │ │ │                      shutdown => brutal_kill,
        │ │ │                      type => worker,
        │ │ │ -                    modules => [ch3]}],
        │ │ │ -    {ok, {SupFlags, ChildSpecs}}.

        The SupFlags variable in the return value from init/1 represents the │ │ │ + modules => [ch3]}], │ │ │ + {ok, {SupFlags, ChildSpecs}}.

        The SupFlags variable in the return value from init/1 represents the │ │ │ supervisor flags.

        The ChildSpecs variable in the return value from init/1 is a list of │ │ │ child specifications.

        │ │ │ │ │ │ │ │ │ │ │ │ Supervisor Flags │ │ │

        │ │ │ -

        This is the type definition for the supervisor flags:

        sup_flags() = #{strategy => strategy(),           % optional
        │ │ │ -                intensity => non_neg_integer(),   % optional
        │ │ │ -                period => pos_integer(),          % optional
        │ │ │ -                auto_shutdown => auto_shutdown()} % optional
        │ │ │ -    strategy() = one_for_all
        │ │ │ +

        This is the type definition for the supervisor flags:

        sup_flags() = #{strategy => strategy(),           % optional
        │ │ │ +                intensity => non_neg_integer(),   % optional
        │ │ │ +                period => pos_integer(),          % optional
        │ │ │ +                auto_shutdown => auto_shutdown()} % optional
        │ │ │ +    strategy() = one_for_all
        │ │ │                 | one_for_one
        │ │ │                 | rest_for_one
        │ │ │                 | simple_one_for_one
        │ │ │ -    auto_shutdown() = never
        │ │ │ +    auto_shutdown() = never
        │ │ │                      | any_significant
        │ │ │                      | all_significant

        │ │ │ │ │ │ │ │ │ │ │ │ @@ -408,28 +408,28 @@ │ │ │ exhaust the Maximum Restart Intensity of the │ │ │ parent supervisor.

        │ │ │ │ │ │ │ │ │ │ │ │ Child Specification │ │ │

        │ │ │ -

        The type definition for a child specification is as follows:

        child_spec() = #{id => child_id(),             % mandatory
        │ │ │ -                 start => mfargs(),            % mandatory
        │ │ │ -                 restart => restart(),         % optional
        │ │ │ -                 significant => significant(), % optional
        │ │ │ -                 shutdown => shutdown(),       % optional
        │ │ │ -                 type => worker(),             % optional
        │ │ │ -                 modules => modules()}         % optional
        │ │ │ -    child_id() = term()
        │ │ │ -    mfargs() = {M :: module(), F :: atom(), A :: [term()]}
        │ │ │ -    modules() = [module()] | dynamic
        │ │ │ -    restart() = permanent | transient | temporary
        │ │ │ -    significant() = boolean()
        │ │ │ -    shutdown() = brutal_kill | timeout()
        │ │ │ -    worker() = worker | supervisor
        • id is used to identify the child specification internally by the supervisor.

          The id key is mandatory.

          Note that this identifier occasionally has been called "name". As far as │ │ │ +

          The type definition for a child specification is as follows:

          child_spec() = #{id => child_id(),             % mandatory
          │ │ │ +                 start => mfargs(),            % mandatory
          │ │ │ +                 restart => restart(),         % optional
          │ │ │ +                 significant => significant(), % optional
          │ │ │ +                 shutdown => shutdown(),       % optional
          │ │ │ +                 type => worker(),             % optional
          │ │ │ +                 modules => modules()}         % optional
          │ │ │ +    child_id() = term()
          │ │ │ +    mfargs() = {M :: module(), F :: atom(), A :: [term()]}
          │ │ │ +    modules() = [module()] | dynamic
          │ │ │ +    restart() = permanent | transient | temporary
          │ │ │ +    significant() = boolean()
          │ │ │ +    shutdown() = brutal_kill | timeout()
          │ │ │ +    worker() = worker | supervisor
          • id is used to identify the child specification internally by the supervisor.

            The id key is mandatory.

            Note that this identifier occasionally has been called "name". As far as │ │ │ possible, the terms "identifier" or "id" are now used but in order to keep │ │ │ backwards compatibility, some occurrences of "name" can still be found, for │ │ │ example in error messages.

          • start defines the function call used to start the child process. It is a │ │ │ module-function-arguments tuple used as apply(M, F, A).

            It is to be (or result in) a call to any of the following:

            The start key is mandatory.

          • restart defines when a terminated child process is to be │ │ │ restarted.

            • A permanent child process is always restarted.
            • A temporary child process is never restarted (not even when the supervisor │ │ │ restart strategy is rest_for_one or one_for_all and a sibling death │ │ │ @@ -457,53 +457,53 @@ │ │ │ supervisor, the default value infinity will be used.

            • type specifies whether the child process is a supervisor or a worker.

              The type key is optional. If it is not given, the default value worker │ │ │ will be used.

            • modules has to be a list consisting of a single element. The value │ │ │ of that element depends on the behaviour of the process:

              • If the child process is a gen_event, the element has to be the atom │ │ │ dynamic.
              • Otherwise, the element should be Module, where Module is the │ │ │ name of the callback module.

              This information is used by the release handler during upgrades and │ │ │ downgrades; see Release Handling.

              The modules key is optional. If it is not given, it defaults to [M], where │ │ │ M comes from the child's start {M,F,A}.

            Example: The child specification to start the server ch3 in the previous │ │ │ -example look as follows:

            #{id => ch3,
            │ │ │ -  start => {ch3, start_link, []},
            │ │ │ +example look as follows:

            #{id => ch3,
            │ │ │ +  start => {ch3, start_link, []},
            │ │ │    restart => permanent,
            │ │ │    shutdown => brutal_kill,
            │ │ │    type => worker,
            │ │ │ -  modules => [ch3]}

            or simplified, relying on the default values:

            #{id => ch3,
            │ │ │ +  modules => [ch3]}

            or simplified, relying on the default values:

            #{id => ch3,
            │ │ │    start => {ch3, start_link, []},
            │ │ │    shutdown => brutal_kill}

            Example: A child specification to start the event manager from the chapter about │ │ │ -gen_event:

            #{id => error_man,
            │ │ │ -  start => {gen_event, start_link, [{local, error_man}]},
            │ │ │ -  modules => dynamic}

            Both server and event manager are registered processes which can be expected to │ │ │ +gen_event:

            #{id => error_man,
            │ │ │ +  start => {gen_event, start_link, [{local, error_man}]},
            │ │ │ +  modules => dynamic}

            Both server and event manager are registered processes which can be expected to │ │ │ be always accessible. Thus they are specified to be permanent.

            ch3 does not need to do any cleaning up before termination. Thus, no shutdown │ │ │ time is needed, but brutal_kill is sufficient. error_man can need some time │ │ │ for the event handlers to clean up, thus the shutdown time is set to 5000 ms │ │ │ -(which is the default value).

            Example: A child specification to start another supervisor:

            #{id => sup,
            │ │ │ -  start => {sup, start_link, []},
            │ │ │ +(which is the default value).

            Example: A child specification to start another supervisor:

            #{id => sup,
            │ │ │ +  start => {sup, start_link, []},
            │ │ │    restart => transient,
            │ │ │ -  type => supervisor} % will cause default shutdown=>infinity

            │ │ │ + type => supervisor} % will cause default shutdown=>infinity

            │ │ │ │ │ │ │ │ │ │ │ │ Starting a Supervisor │ │ │

            │ │ │

            In the previous example, the supervisor is started by calling │ │ │ -ch_sup:start_link():

            start_link() ->
            │ │ │ -    supervisor:start_link(ch_sup, []).

            ch_sup:start_link calls function supervisor:start_link/2, which spawns and │ │ │ +ch_sup:start_link():

            start_link() ->
            │ │ │ +    supervisor:start_link(ch_sup, []).

            ch_sup:start_link calls function supervisor:start_link/2, which spawns and │ │ │ links to a new process, a supervisor.

            • The first argument, ch_sup, is the name of the callback module, that is, the │ │ │ module where the init callback function is located.
            • The second argument, [], is a term that is passed as is to the callback │ │ │ function init. Here, init does not need any data and ignores the argument.

            In this case, the supervisor is not registered. Instead its pid must be used. A │ │ │ name can be specified by calling │ │ │ supervisor:start_link({local, Name}, Module, Args) │ │ │ or │ │ │ supervisor:start_link({global, Name}, Module, Args).

            The new supervisor process calls the callback function ch_sup:init([]). init │ │ │ -has to return {ok, {SupFlags, ChildSpecs}}:

            init(_Args) ->
            │ │ │ -    SupFlags = #{},
            │ │ │ -    ChildSpecs = [#{id => ch3,
            │ │ │ -                    start => {ch3, start_link, []},
            │ │ │ -                    shutdown => brutal_kill}],
            │ │ │ -    {ok, {SupFlags, ChildSpecs}}.

            Subsequently, the supervisor starts its child processes according to the child │ │ │ +has to return {ok, {SupFlags, ChildSpecs}}:

            init(_Args) ->
            │ │ │ +    SupFlags = #{},
            │ │ │ +    ChildSpecs = [#{id => ch3,
            │ │ │ +                    start => {ch3, start_link, []},
            │ │ │ +                    shutdown => brutal_kill}],
            │ │ │ +    {ok, {SupFlags, ChildSpecs}}.

            Subsequently, the supervisor starts its child processes according to the child │ │ │ specifications in the start specification. In this case there is a single child │ │ │ process, called ch3.

            supervisor:start_link/3 is synchronous. It does not return until all child │ │ │ processes have been started.

            │ │ │ │ │ │ │ │ │ │ │ │ Adding a Child Process │ │ │ @@ -532,31 +532,31 @@ │ │ │ │ │ │ │ │ │ Simplified one_for_one Supervisors │ │ │

            │ │ │

            A supervisor with restart strategy simple_one_for_one is a simplified │ │ │ one_for_one supervisor, where all child processes are dynamically added │ │ │ instances of the same process.

            The following is an example of a callback module for a simple_one_for_one │ │ │ -supervisor:

            -module(simple_sup).
            │ │ │ --behaviour(supervisor).
            │ │ │ +supervisor:

            -module(simple_sup).
            │ │ │ +-behaviour(supervisor).
            │ │ │  
            │ │ │ --export([start_link/0]).
            │ │ │ --export([init/1]).
            │ │ │ +-export([start_link/0]).
            │ │ │ +-export([init/1]).
            │ │ │  
            │ │ │ -start_link() ->
            │ │ │ -    supervisor:start_link(simple_sup, []).
            │ │ │ +start_link() ->
            │ │ │ +    supervisor:start_link(simple_sup, []).
            │ │ │  
            │ │ │ -init(_Args) ->
            │ │ │ -    SupFlags = #{strategy => simple_one_for_one,
            │ │ │ +init(_Args) ->
            │ │ │ +    SupFlags = #{strategy => simple_one_for_one,
            │ │ │                   intensity => 0,
            │ │ │ -                 period => 1},
            │ │ │ -    ChildSpecs = [#{id => call,
            │ │ │ -                    start => {call, start_link, []},
            │ │ │ -                    shutdown => brutal_kill}],
            │ │ │ -    {ok, {SupFlags, ChildSpecs}}.

            When started, the supervisor does not start any child │ │ │ + period => 1}, │ │ │ + ChildSpecs = [#{id => call, │ │ │ + start => {call, start_link, []}, │ │ │ + shutdown => brutal_kill}], │ │ │ + {ok, {SupFlags, ChildSpecs}}.

            When started, the supervisor does not start any child │ │ │ processes. Instead, all child processes need to be added dynamically by │ │ │ calling supervisor:start_child(Sup, List).

            Sup is the pid, or name, of the supervisor. List is an arbitrary list of │ │ │ terms, which are added to the list of arguments specified in the child │ │ │ specification. If the start function is specified as {M, F, A}, the child │ │ │ process is started by calling apply(M, F, A++List).

            For example, adding a child to simple_sup above:

            supervisor:start_child(Pid, [id1])

            The result is that the child process is started by calling │ │ │ apply(call, start_link, []++[id1]), or actually:

            call:start_link(id1)

            A child under a simple_one_for_one supervisor can be terminated with the │ │ │ following:

            supervisor:terminate_child(Sup, Pid)

            Sup is the pid, or name, of the supervisor and Pid is the pid of the child.

            Because a simple_one_for_one supervisor can have many children, it shuts them │ │ ├── ./usr/share/doc/erlang-doc/html/doc/system/tablesdatabases.html │ │ │ @@ -146,73 +146,73 @@ │ │ │ │ │ │ │ │ │ Deleting an Element │ │ │

        │ │ │

        The delete operation is considered successful if the element was not present │ │ │ in the table. Hence all attempts to check that the element is present in the │ │ │ Ets/Mnesia table before deletion are unnecessary. Here follows an example for │ │ │ -Ets tables:

        DO

        ets:delete(Tab, Key),

        DO NOT

        case ets:lookup(Tab, Key) of
        │ │ │ -    [] ->
        │ │ │ +Ets tables:

        DO

        ets:delete(Tab, Key),

        DO NOT

        case ets:lookup(Tab, Key) of
        │ │ │ +    [] ->
        │ │ │          ok;
        │ │ │ -    [_|_] ->
        │ │ │ -        ets:delete(Tab, Key)
        │ │ │ +    [_|_] ->
        │ │ │ +        ets:delete(Tab, Key)
        │ │ │  end,

        │ │ │ │ │ │ │ │ │ │ │ │ Fetching Data │ │ │

        │ │ │

        Do not fetch data that you already have.

        Consider that you have a module that handles the abstract data type Person. │ │ │ You export the interface function print_person/1, which uses the internal │ │ │ functions print_name/1, print_age/1, and print_occupation/1.

        Note

        If the function print_name/1, and so on, had been interface functions, the │ │ │ situation would have been different, as you do not want the user of the │ │ │ interface to know about the internal data representation.

        DO

        %%% Interface function
        │ │ │ -print_person(PersonId) ->
        │ │ │ +print_person(PersonId) ->
        │ │ │      %% Look up the person in the named table person,
        │ │ │ -    case ets:lookup(person, PersonId) of
        │ │ │ -        [Person] ->
        │ │ │ -            print_name(Person),
        │ │ │ -            print_age(Person),
        │ │ │ -            print_occupation(Person);
        │ │ │ -        [] ->
        │ │ │ -            io:format("No person with ID = ~p~n", [PersonID])
        │ │ │ +    case ets:lookup(person, PersonId) of
        │ │ │ +        [Person] ->
        │ │ │ +            print_name(Person),
        │ │ │ +            print_age(Person),
        │ │ │ +            print_occupation(Person);
        │ │ │ +        [] ->
        │ │ │ +            io:format("No person with ID = ~p~n", [PersonID])
        │ │ │      end.
        │ │ │  
        │ │ │  %%% Internal functions
        │ │ │ -print_name(Person) ->
        │ │ │ -    io:format("No person ~p~n", [Person#person.name]).
        │ │ │ +print_name(Person) ->
        │ │ │ +    io:format("No person ~p~n", [Person#person.name]).
        │ │ │  
        │ │ │ -print_age(Person) ->
        │ │ │ -    io:format("No person ~p~n", [Person#person.age]).
        │ │ │ +print_age(Person) ->
        │ │ │ +    io:format("No person ~p~n", [Person#person.age]).
        │ │ │  
        │ │ │ -print_occupation(Person) ->
        │ │ │ -    io:format("No person ~p~n", [Person#person.occupation]).

        DO NOT

        %%% Interface function
        │ │ │ -print_person(PersonId) ->
        │ │ │ +print_occupation(Person) ->
        │ │ │ +    io:format("No person ~p~n", [Person#person.occupation]).

        DO NOT

        %%% Interface function
        │ │ │ +print_person(PersonId) ->
        │ │ │      %% Look up the person in the named table person,
        │ │ │ -    case ets:lookup(person, PersonId) of
        │ │ │ -        [Person] ->
        │ │ │ -            print_name(PersonID),
        │ │ │ -            print_age(PersonID),
        │ │ │ -            print_occupation(PersonID);
        │ │ │ -        [] ->
        │ │ │ -            io:format("No person with ID = ~p~n", [PersonID])
        │ │ │ +    case ets:lookup(person, PersonId) of
        │ │ │ +        [Person] ->
        │ │ │ +            print_name(PersonID),
        │ │ │ +            print_age(PersonID),
        │ │ │ +            print_occupation(PersonID);
        │ │ │ +        [] ->
        │ │ │ +            io:format("No person with ID = ~p~n", [PersonID])
        │ │ │      end.
        │ │ │  
        │ │ │  %%% Internal functions
        │ │ │ -print_name(PersonID) ->
        │ │ │ -    [Person] = ets:lookup(person, PersonId),
        │ │ │ -    io:format("No person ~p~n", [Person#person.name]).
        │ │ │ -
        │ │ │ -print_age(PersonID) ->
        │ │ │ -    [Person] = ets:lookup(person, PersonId),
        │ │ │ -    io:format("No person ~p~n", [Person#person.age]).
        │ │ │ -
        │ │ │ -print_occupation(PersonID) ->
        │ │ │ -    [Person] = ets:lookup(person, PersonId),
        │ │ │ -    io:format("No person ~p~n", [Person#person.occupation]).

        │ │ │ +print_name(PersonID) -> │ │ │ + [Person] = ets:lookup(person, PersonId), │ │ │ + io:format("No person ~p~n", [Person#person.name]). │ │ │ + │ │ │ +print_age(PersonID) -> │ │ │ + [Person] = ets:lookup(person, PersonId), │ │ │ + io:format("No person ~p~n", [Person#person.age]). │ │ │ + │ │ │ +print_occupation(PersonID) -> │ │ │ + [Person] = ets:lookup(person, PersonId), │ │ │ + io:format("No person ~p~n", [Person#person.occupation]).

        │ │ │ │ │ │ │ │ │ │ │ │ Non-Persistent Database Storage │ │ │

        │ │ │

        For non-persistent database storage, prefer Ets tables over Mnesia │ │ │ local_content tables. Even the Mnesia dirty_write operations carry a fixed │ │ │ @@ -226,38 +226,38 @@ │ │ │ │ │ │

        Assuming an Ets table that uses idno as key and contains the following:

        [#person{idno = 1, name = "Adam",  age = 31, occupation = "mailman"},
        │ │ │   #person{idno = 2, name = "Bryan", age = 31, occupation = "cashier"},
        │ │ │   #person{idno = 3, name = "Bryan", age = 35, occupation = "banker"},
        │ │ │   #person{idno = 4, name = "Carl",  age = 25, occupation = "mailman"}]

        If you must return all data stored in the Ets table, you can use │ │ │ ets:tab2list/1. However, usually you are only interested in a subset of the │ │ │ information in which case ets:tab2list/1 is expensive. If you only want to │ │ │ -extract one field from each record, for example, the age of every person, then:

        DO

        ets:select(Tab, [{#person{idno='_',
        │ │ │ +extract one field from each record, for example, the age of every person, then:

        DO

        ets:select(Tab, [{#person{idno='_',
        │ │ │                            name='_',
        │ │ │                            age='$1',
        │ │ │ -                          occupation = '_'},
        │ │ │ -                [],
        │ │ │ -                ['$1']}]),

        DO NOT

        TabList = ets:tab2list(Tab),
        │ │ │ -lists:map(fun(X) -> X#person.age end, TabList),

        If you are only interested in the age of all persons named "Bryan", then:

        DO

        ets:select(Tab, [{#person{idno='_',
        │ │ │ +                          occupation = '_'},
        │ │ │ +                [],
        │ │ │ +                ['$1']}]),

        DO NOT

        TabList = ets:tab2list(Tab),
        │ │ │ +lists:map(fun(X) -> X#person.age end, TabList),

        If you are only interested in the age of all persons named "Bryan", then:

        DO

        ets:select(Tab, [{#person{idno='_',
        │ │ │                            name="Bryan",
        │ │ │                            age='$1',
        │ │ │ -                          occupation = '_'},
        │ │ │ -                [],
        │ │ │ -                ['$1']}])

        DO NOT

        TabList = ets:tab2list(Tab),
        │ │ │ -lists:foldl(fun(X, Acc) -> case X#person.name of
        │ │ │ +                          occupation = '_'},
        │ │ │ +                [],
        │ │ │ +                ['$1']}])

        DO NOT

        TabList = ets:tab2list(Tab),
        │ │ │ +lists:foldl(fun(X, Acc) -> case X#person.name of
        │ │ │                                  "Bryan" ->
        │ │ │ -                                    [X#person.age|Acc];
        │ │ │ +                                    [X#person.age|Acc];
        │ │ │                                   _ ->
        │ │ │                                       Acc
        │ │ │                             end
        │ │ │ -             end, [], TabList)

        If you need all information stored in the Ets table about persons named "Bryan", │ │ │ -then:

        DO

        ets:select(Tab, [{#person{idno='_',
        │ │ │ +             end, [], TabList)

        If you need all information stored in the Ets table about persons named "Bryan", │ │ │ +then:

        DO

        ets:select(Tab, [{#person{idno='_',
        │ │ │                            name="Bryan",
        │ │ │                            age='_',
        │ │ │ -                          occupation = '_'}, [], ['$_']}]),

        DO NOT

        TabList = ets:tab2list(Tab),
        │ │ │ -lists:filter(fun(X) -> X#person.name == "Bryan" end, TabList),

        │ │ │ + occupation = '_'}, [], ['$_']}]),

        DO NOT

        TabList = ets:tab2list(Tab),
        │ │ │ +lists:filter(fun(X) -> X#person.name == "Bryan" end, TabList),

        │ │ │ │ │ │ │ │ │ │ │ │ ordered_set Tables │ │ │

        │ │ │

        If the data in the table is to be accessed so that the order of the keys in the │ │ │ table is significant, the table type ordered_set can be used instead of the │ │ │ @@ -293,20 +293,20 @@ │ │ │ Clearly, the second table would have to be kept consistent with the master │ │ │ table. Mnesia can do this for you, but a home-brew index table can be very │ │ │ efficient compared to the overhead involved in using Mnesia.

        An index table for the table in the previous examples would have to be a bag (as │ │ │ keys would appear more than once) and can have the following contents:

        [#index_entry{name="Adam", idno=1},
        │ │ │   #index_entry{name="Bryan", idno=2},
        │ │ │   #index_entry{name="Bryan", idno=3},
        │ │ │   #index_entry{name="Carl", idno=4}]

        Given this index table, a lookup of the age fields for all persons named │ │ │ -"Bryan" can be done as follows:

        MatchingIDs = ets:lookup(IndexTable,"Bryan"),
        │ │ │ -lists:map(fun(#index_entry{idno = ID}) ->
        │ │ │ -                 [#person{age = Age}] = ets:lookup(PersonTable, ID),
        │ │ │ +"Bryan" can be done as follows:

        MatchingIDs = ets:lookup(IndexTable,"Bryan"),
        │ │ │ +lists:map(fun(#index_entry{idno = ID}) ->
        │ │ │ +                 [#person{age = Age}] = ets:lookup(PersonTable, ID),
        │ │ │                   Age
        │ │ │            end,
        │ │ │ -          MatchingIDs),

        Notice that this code does not use ets:match/2, but instead uses the │ │ │ + MatchingIDs),

        Notice that this code does not use ets:match/2, but instead uses the │ │ │ ets:lookup/2 call. The lists:map/2 call is only used to traverse the idnos │ │ │ matching the name "Bryan" in the table; thus the number of lookups in the master │ │ │ table is minimized.

        Keeping an index table introduces some overhead when inserting records in the │ │ │ table. The number of operations gained from the table must therefore be compared │ │ │ against the number of operations inserting objects in the table. However, notice │ │ │ that the gain is significant when the key can be used to lookup elements.

        │ │ │ │ │ │ @@ -321,51 +321,51 @@ │ │ │ Secondary Index │ │ │

        │ │ │

        If you frequently do lookups on a field that is not the key of the table, you │ │ │ lose performance using mnesia:select() or │ │ │ mnesia:match_object() as these function traverse │ │ │ the whole table. Instead, you can create a secondary index and use │ │ │ mnesia:index_read/3 to get faster access at the expense of using more │ │ │ -memory.

        Example:

        -record(person, {idno, name, age, occupation}).
        │ │ │ +memory.

        Example:

        -record(person, {idno, name, age, occupation}).
        │ │ │          ...
        │ │ │ -{atomic, ok} =
        │ │ │ -mnesia:create_table(person, [{index,[#person.age]},
        │ │ │ -                              {attributes,
        │ │ │ -                                    record_info(fields, person)}]),
        │ │ │ -{atomic, ok} = mnesia:add_table_index(person, age),
        │ │ │ +{atomic, ok} =
        │ │ │ +mnesia:create_table(person, [{index,[#person.age]},
        │ │ │ +                              {attributes,
        │ │ │ +                                    record_info(fields, person)}]),
        │ │ │ +{atomic, ok} = mnesia:add_table_index(person, age),
        │ │ │  ...
        │ │ │  
        │ │ │  PersonsAge42 =
        │ │ │ -     mnesia:dirty_index_read(person, 42, #person.age),

        │ │ │ + mnesia:dirty_index_read(person, 42, #person.age),

        │ │ │ │ │ │ │ │ │ │ │ │ Transactions │ │ │

        │ │ │

        Using transactions is a way to guarantee that the distributed Mnesia database │ │ │ remains consistent, even when many different processes update it in parallel. │ │ │ However, if you have real-time requirements it is recommended to use dirtry │ │ │ operations instead of transactions. When using dirty operations, you lose the │ │ │ consistency guarantee; this is usually solved by only letting one process update │ │ │ the table. Other processes must send update requests to that process.

        Example:

        ...
        │ │ │  %% Using transaction
        │ │ │  
        │ │ │ -Fun = fun() ->
        │ │ │ -          [mnesia:read({Table, Key}),
        │ │ │ -           mnesia:read({Table2, Key2})]
        │ │ │ +Fun = fun() ->
        │ │ │ +          [mnesia:read({Table, Key}),
        │ │ │ +           mnesia:read({Table2, Key2})]
        │ │ │        end,
        │ │ │  
        │ │ │ -{atomic, [Result1, Result2]}  = mnesia:transaction(Fun),
        │ │ │ +{atomic, [Result1, Result2]}  = mnesia:transaction(Fun),
        │ │ │  ...
        │ │ │  
        │ │ │  %% Same thing using dirty operations
        │ │ │  ...
        │ │ │  
        │ │ │ -Result1 = mnesia:dirty_read({Table, Key}),
        │ │ │ -Result2 = mnesia:dirty_read({Table2, Key2}),
        │ │ │ +
        Result1 = mnesia:dirty_read({Table, Key}), │ │ │ +Result2 = mnesia:dirty_read({Table2, Key2}),
        │ │ │ │ │ │ │ │ │
        │ │ │
        │ │ │ │ │ │

        map/0 type.

        For convenience, the following types are also built-in. They can be thought as │ │ │ predefined aliases for the type unions also shown in the table.

        Built-in typeDefined as
        term/0any/0
        binary/0<<_:_*8>>
        nonempty_binary/0<<_:8, _:_*8>>
        bitstring/0<<_:_*1>>
        nonempty_bitstring/0<<_:1, _:_*1>>
        boolean/0'false' | 'true'
        byte/00..255
        char/00..16#10ffff
        nil/0[]
        number/0integer/0 | float/0
        list/0[any()]
        maybe_improper_list/0maybe_improper_list(any(), any())
        nonempty_list/0nonempty_list(any())
        string/0[char()]
        nonempty_string/0[char(),...]
        iodata/0iolist() | binary()
        iolist/0maybe_improper_list(byte() | binary() | iolist(), binary() | [])
        map/0#{any() => any()}
        function/0fun()
        module/0atom/0
        mfa/0{module(),atom(),arity()}
        arity/00..255
        identifier/0pid() | port() | reference()
        node/0atom/0
        timeout/0'infinity' | non_neg_integer()
        no_return/0none/0

        Table: Built-in types, predefined aliases

        In addition, the following three built-in types exist and can be thought as │ │ │ defined below, though strictly their "type definition" is not valid syntax │ │ │ according to the type language defined above.

        Built-in typeCan be thought defined by the syntax
        non_neg_integer/00..
        pos_integer/01..
        neg_integer/0..-1

        Table: Additional built-in types

        Note

        The following built-in list types also exist, but they are expected to be │ │ │ -rarely used. Hence, they have long names:

        nonempty_maybe_improper_list() :: nonempty_maybe_improper_list(any(), any())
        │ │ │ -nonempty_improper_list(Type1, Type2)
        │ │ │ -nonempty_maybe_improper_list(Type1, Type2)

        where the last two types define the set of Erlang terms one would expect.

        Also for convenience, record notation is allowed to be used. Records are │ │ │ -shorthands for the corresponding tuples:

        Record :: #Erlang_Atom{}
        │ │ │ -        | #Erlang_Atom{Fields}

        Records are extended to possibly contain type information. This is described in │ │ │ +rarely used. Hence, they have long names:

        nonempty_maybe_improper_list() :: nonempty_maybe_improper_list(any(), any())
        │ │ │ +nonempty_improper_list(Type1, Type2)
        │ │ │ +nonempty_maybe_improper_list(Type1, Type2)

        where the last two types define the set of Erlang terms one would expect.

        Also for convenience, record notation is allowed to be used. Records are │ │ │ +shorthands for the corresponding tuples:

        Record :: #Erlang_Atom{}
        │ │ │ +        | #Erlang_Atom{Fields}

        Records are extended to possibly contain type information. This is described in │ │ │ Type Information in Record Declarations.

        │ │ │ │ │ │ │ │ │ │ │ │ Redefining built-in types │ │ │

        │ │ │

        Change

        Starting from Erlang/OTP 26, it is permitted to define a type having the same │ │ │ name as a built-in type.

        It is recommended to avoid deliberately reusing built-in names because it can be │ │ │ confusing. However, when an Erlang/OTP release introduces a new type, code that │ │ │ happened to define its own type having the same name will continue to work.

        As an example, imagine that the Erlang/OTP 42 release introduces a new type │ │ │ -gadget() defined like this:

        -type gadget() :: {'gadget', reference()}.

        Further imagine that some code has its own (different) definition of gadget(), │ │ │ -for example:

        -type gadget() :: #{}.

        Since redefinitions are allowed, the code will still compile (but with a │ │ │ +gadget() defined like this:

        -type gadget() :: {'gadget', reference()}.

        Further imagine that some code has its own (different) definition of gadget(), │ │ │ +for example:

        -type gadget() :: #{}.

        Since redefinitions are allowed, the code will still compile (but with a │ │ │ warning), and Dialyzer will not emit any additional warnings.

        │ │ │ │ │ │ │ │ │ │ │ │ Type Declarations of User-Defined Types │ │ │

        │ │ │

        As seen, the basic syntax of a type is an atom followed by closed parentheses. │ │ │ New types are declared using -type and -opaque attributes as in the │ │ │ -following:

        -type my_struct_type() :: Type.
        │ │ │ --opaque my_opaq_type() :: Type.

        The type name is the atom my_struct_type, followed by parentheses. Type is a │ │ │ +following:

        -type my_struct_type() :: Type.
        │ │ │ +-opaque my_opaq_type() :: Type.

        The type name is the atom my_struct_type, followed by parentheses. Type is a │ │ │ type as defined in the previous section. A current restriction is that Type │ │ │ can contain only predefined types, or user-defined types which are either of the │ │ │ following:

        • Module-local type, that is, with a definition that is present in the code of │ │ │ the module
        • Remote type, that is, type defined in, and exported by, other modules; more │ │ │ about this soon.

        For module-local types, the restriction that their definition exists in the │ │ │ module is enforced by the compiler and results in a compilation error. (A │ │ │ similar restriction currently exists for records.)

        Type declarations can also be parameterized by including type variables between │ │ │ the parentheses. The syntax of type variables is the same as Erlang variables, │ │ │ that is, starts with an upper-case letter. These variables is to │ │ │ -appear on the RHS of the definition. A concrete example follows:

        -type orddict(Key, Val) :: [{Key, Val}].

        A module can export some types to declare that other modules are allowed to │ │ │ -refer to them as remote types. This declaration has the following form:

        -export_type([T1/A1, ..., Tk/Ak]).

        Here the Tis are atoms (the name of the type) and the Ais are their arguments.

        Example:

        -export_type([my_struct_type/0, orddict/2]).

        Assuming that these types are exported from module 'mod', you can refer to │ │ │ -them from other modules using remote type expressions like the following:

        mod:my_struct_type()
        │ │ │ -mod:orddict(atom(), term())

        It is not allowed to refer to types that are not declared as exported.

        Types declared as opaque represent sets of terms whose structure is not │ │ │ +appear on the RHS of the definition. A concrete example follows:

        -type orddict(Key, Val) :: [{Key, Val}].

        A module can export some types to declare that other modules are allowed to │ │ │ +refer to them as remote types. This declaration has the following form:

        -export_type([T1/A1, ..., Tk/Ak]).

        Here the Tis are atoms (the name of the type) and the Ais are their arguments.

        Example:

        -export_type([my_struct_type/0, orddict/2]).

        Assuming that these types are exported from module 'mod', you can refer to │ │ │ +them from other modules using remote type expressions like the following:

        mod:my_struct_type()
        │ │ │ +mod:orddict(atom(), term())

        It is not allowed to refer to types that are not declared as exported.

        Types declared as opaque represent sets of terms whose structure is not │ │ │ supposed to be visible from outside of their defining module. That is, only the │ │ │ module defining them is allowed to depend on their term structure. Consequently, │ │ │ such types do not make much sense as module local - module local types are not │ │ │ accessible by other modules anyway - and is always to be exported.

        Read more on Opaques

        │ │ │ │ │ │ │ │ │ │ │ │ Type Information in Record Declarations │ │ │

        │ │ │

        The types of record fields can be specified in the declaration of the record. │ │ │ -The syntax for this is as follows:

        -record(rec, {field1 :: Type1, field2, field3 :: Type3}).

        For fields without type annotations, their type defaults to any(). That is, the │ │ │ -previous example is a shorthand for the following:

        -record(rec, {field1 :: Type1, field2 :: any(), field3 :: Type3}).

        In the presence of initial values for fields, the type must be declared after │ │ │ -the initialization, as follows:

        -record(rec, {field1 = [] :: Type1, field2, field3 = 42 :: Type3}).

        The initial values for fields are to be compatible with (that is, a member of) │ │ │ +The syntax for this is as follows:

        -record(rec, {field1 :: Type1, field2, field3 :: Type3}).

        For fields without type annotations, their type defaults to any(). That is, the │ │ │ +previous example is a shorthand for the following:

        -record(rec, {field1 :: Type1, field2 :: any(), field3 :: Type3}).

        In the presence of initial values for fields, the type must be declared after │ │ │ +the initialization, as follows:

        -record(rec, {field1 = [] :: Type1, field2, field3 = 42 :: Type3}).

        The initial values for fields are to be compatible with (that is, a member of) │ │ │ the corresponding types. This is checked by the compiler and results in a │ │ │ compilation error if a violation is detected.

        Change

        Before Erlang/OTP 19, for fields without initial values, the singleton type │ │ │ 'undefined' was added to all declared types. In other words, the following │ │ │ -two record declarations had identical effects:

        -record(rec, {f1 = 42 :: integer(),
        │ │ │ -             f2      :: float(),
        │ │ │ -             f3      :: 'a' | 'b'}).
        │ │ │ +two record declarations had identical effects:

        -record(rec, {f1 = 42 :: integer(),
        │ │ │ +             f2      :: float(),
        │ │ │ +             f3      :: 'a' | 'b'}).
        │ │ │  
        │ │ │ --record(rec, {f1 = 42 :: integer(),
        │ │ │ -              f2      :: 'undefined' | float(),
        │ │ │ -              f3      :: 'undefined' | 'a' | 'b'}).

        This is no longer the case. If you require 'undefined' in your record field │ │ │ +-record(rec, {f1 = 42 :: integer(), │ │ │ + f2 :: 'undefined' | float(), │ │ │ + f3 :: 'undefined' | 'a' | 'b'}).

        This is no longer the case. If you require 'undefined' in your record field │ │ │ type, you must explicitly add it to the typespec, as in the 2nd example.

        Any record, containing type information or not, once defined, can be used as a │ │ │ type using the following syntax:

        #rec{}

        In addition, the record fields can be further specified when using a record type │ │ │ by adding type information about the field as follows:

        #rec{some_field :: Type}

        Any unspecified fields are assumed to have the type in the original record │ │ │ declaration.

        Note

        When records are used to create patterns for ETS and Mnesia match functions, │ │ │ -Dialyzer may need some help not to emit bad warnings. For example:

        -type height() :: pos_integer().
        │ │ │ --record(person, {name :: string(), height :: height()}).
        │ │ │ +Dialyzer may need some help not to emit bad warnings. For example:

        -type height() :: pos_integer().
        │ │ │ +-record(person, {name :: string(), height :: height()}).
        │ │ │  
        │ │ │ -lookup(Name, Tab) ->
        │ │ │ -    ets:match_object(Tab, #person{name = Name, _ = '_'}).

        Dialyzer will emit a warning since '_' is not in the type of record field │ │ │ +lookup(Name, Tab) -> │ │ │ + ets:match_object(Tab, #person{name = Name, _ = '_'}).

        Dialyzer will emit a warning since '_' is not in the type of record field │ │ │ height.

        The recommended way of dealing with this is to declare the smallest record │ │ │ field types to accommodate all your needs, and then create refinements as │ │ │ -needed. The modified example:

        -record(person, {name :: string(), height :: height() | '_'}).
        │ │ │ +needed. The modified example:

        -record(person, {name :: string(), height :: height() | '_'}).
        │ │ │  
        │ │ │ --type person() :: #person{height :: height()}.

        In specifications and type declarations the type person() is to be preferred │ │ │ +-type person() :: #person{height :: height()}.

        In specifications and type declarations the type person() is to be preferred │ │ │ before #person{}.

        │ │ │ │ │ │ │ │ │ │ │ │ Specifications for Functions │ │ │

        │ │ │

        A specification (or contract) for a function is given using the -spec │ │ │ attribute. The general format is as follows:

        -spec Function(ArgType1, ..., ArgTypeN) -> ReturnType.

        An implementation of the function with the same name Function must exist in │ │ │ the current module, and the arity of the function must match the number of │ │ │ arguments, otherwise the compilation fails.

        The following longer format with module name is also valid as long as Module │ │ │ is the name of the current module. This can be useful for documentation │ │ │ purposes.

        -spec Module:Function(ArgType1, ..., ArgTypeN) -> ReturnType.

        Also, for documentation purposes, argument names can be given:

        -spec Function(ArgName1 :: Type1, ..., ArgNameN :: TypeN) -> RT.

        A function specification can be overloaded. That is, it can have several types, │ │ │ -separated by a semicolon (;). For example:

        -spec foo(T1, T2) -> T3;
        │ │ │ -         (T4, T5) -> T6.

        A current restriction, which currently results in a warning by Dialyzer, is that │ │ │ +separated by a semicolon (;). For example:

        -spec foo(T1, T2) -> T3;
        │ │ │ +         (T4, T5) -> T6.

        A current restriction, which currently results in a warning by Dialyzer, is that │ │ │ the domains of the argument types cannot overlap. For example, the following │ │ │ -specification results in a warning:

        -spec foo(pos_integer()) -> pos_integer();
        │ │ │ -         (integer()) -> integer().

        Type variables can be used in specifications to specify relations for the input │ │ │ +specification results in a warning:

        -spec foo(pos_integer()) -> pos_integer();
        │ │ │ +         (integer()) -> integer().

        Type variables can be used in specifications to specify relations for the input │ │ │ and output arguments of a function. For example, the following specification │ │ │ defines the type of a polymorphic identity function:

        -spec id(X) -> X.

        Notice that the above specification does not restrict the input and output type │ │ │ in any way. These types can be constrained by guard-like subtype constraints and │ │ │ -provide bounded quantification:

        -spec id(X) -> X when X :: tuple().

        Currently, the :: constraint (read as "is a subtype of") is the only guard │ │ │ +provide bounded quantification:

        -spec id(X) -> X when X :: tuple().

        Currently, the :: constraint (read as "is a subtype of") is the only guard │ │ │ constraint that can be used in the when part of a -spec attribute.

        Note

        The above function specification uses multiple occurrences of the same type │ │ │ variable. That provides more type information than the following function │ │ │ -specification, where the type variables are missing:

        -spec id(tuple()) -> tuple().

        The latter specification says that the function takes some tuple and returns │ │ │ +specification, where the type variables are missing:

        -spec id(tuple()) -> tuple().

        The latter specification says that the function takes some tuple and returns │ │ │ some tuple. The specification with the X type variable specifies that the │ │ │ function takes a tuple and returns the same tuple.

        However, it is up to the tools that process the specifications to choose │ │ │ whether to take this extra information into account or not.

        The scope of a :: constraint is the (...) -> RetType specification after │ │ │ which it appears. To avoid confusion, it is suggested that different variables │ │ │ are used in different constituents of an overloaded contract, as shown in the │ │ │ -following example:

        -spec foo({X, integer()}) -> X when X :: atom();
        │ │ │ -         ([Y]) -> Y when Y :: number().

        Some functions in Erlang are not meant to return; either because they define │ │ │ +following example:

        -spec foo({X, integer()}) -> X when X :: atom();
        │ │ │ +         ([Y]) -> Y when Y :: number().

        Some functions in Erlang are not meant to return; either because they define │ │ │ servers or because they are used to throw exceptions, as in the following │ │ │ -function:

        my_error(Err) -> throw({error, Err}).

        For such functions, it is recommended to use the special no_return/0 type │ │ │ +function:

        my_error(Err) -> throw({error, Err}).

        For such functions, it is recommended to use the special no_return/0 type │ │ │ for their "return", through a contract of the following form:

        -spec my_error(term()) -> no_return().

        Note

        Erlang uses the shorthand version _ as an anonymous type variable equivalent │ │ │ to term/0 or any/0. For example, the following function

        -spec Function(string(), _) -> string().

        is equivalent to:

        -spec Function(string(), any()) -> string().
        │ │ │
        │ │ │ │ │ │
        │ │ │
        │ │ ├── ./usr/share/doc/erlang-doc/html/doc/upcoming_incompatibilities.html │ │ │ @@ -149,45 +149,45 @@ │ │ │ occurrences of maybe without quotes.

        │ │ │ │ │ │ │ │ │ │ │ │ 0.0 and -0.0 will no longer be exactly equal │ │ │

        │ │ │

        Currently, the floating point numbers 0.0 and -0.0 have distinct internal │ │ │ -representations. That can be seen if they are converted to binaries:

        1> <<0.0/float>>.
        │ │ │ -<<0,0,0,0,0,0,0,0>>
        │ │ │ -2> <<-0.0/float>>.
        │ │ │ -<<128,0,0,0,0,0,0,0>>

        However, when they are matched against each other or compared using the =:= │ │ │ +representations. That can be seen if they are converted to binaries:

        1> <<0.0/float>>.
        │ │ │ +<<0,0,0,0,0,0,0,0>>
        │ │ │ +2> <<-0.0/float>>.
        │ │ │ +<<128,0,0,0,0,0,0,0>>

        However, when they are matched against each other or compared using the =:= │ │ │ operator, they are considered to be equal. Thus, 0.0 =:= -0.0 currently │ │ │ returns true.

        In Erlang/OTP 27, 0.0 =:= -0.0 will return false, and matching 0.0 against │ │ │ -0.0 will fail. When used as map keys, 0.0 and -0.0 will be considered to │ │ │ be distinct.

        The == operator will continue to return true for 0.0 == -0.0.

        To help to find code that might need to be revised, in OTP 27 there will be a │ │ │ new compiler warning when matching against 0.0 or comparing to that value │ │ │ using the =:= operator. The warning can be suppressed by matching against │ │ │ +0.0 instead of 0.0.

        We plan to introduce the same warning in OTP 26.1, but by default it will be │ │ │ disabled.

        │ │ │ │ │ │ │ │ │ │ │ │ Singleton type variables will become a compile-time error │ │ │

        │ │ │ -

        Before Erlang/OTP 26, the compiler would silenty accept the following spec:

        -spec f(Opts) -> term() when
        │ │ │ -    Opts :: {ok, Unknown} | {error, Unknown}.
        │ │ │ -f(_) -> error.

        In OTP 26, the compiler emits a warning pointing out that the type variable │ │ │ -Unknown is unbound:

        t.erl:6:18: Warning: type variable 'Unknown' is only used once (is unbound)
        │ │ │ +

        Before Erlang/OTP 26, the compiler would silenty accept the following spec:

        -spec f(Opts) -> term() when
        │ │ │ +    Opts :: {ok, Unknown} | {error, Unknown}.
        │ │ │ +f(_) -> error.

        In OTP 26, the compiler emits a warning pointing out that the type variable │ │ │ +Unknown is unbound:

        t.erl:6:18: Warning: type variable 'Unknown' is only used once (is unbound)
        │ │ │  %    6|     Opts :: {ok, Unknown} | {error, Unknown}.
        │ │ │  %     |                  ^

        In OTP 27, that warning will become an error.

        │ │ │ │ │ │ │ │ │ │ │ │ Escripts will be compiled by default │ │ │

        │ │ │

        Escripts will be compiled by default instead of interpreted. That means that the │ │ │ compiler application must be available.

        The old behavior of interpreting escripts can be restored by adding the │ │ │ -following line to the script file:

        -mode(interpret).

        In OTP 28, support for interpreting an escript will be removed.

        │ │ │ +following line to the script file:

        -mode(interpret).

        In OTP 28, support for interpreting an escript will be removed.

        │ │ │ │ │ │ │ │ │ │ │ │ -code_path_choice will default to strict │ │ │

        │ │ │

        This command line option controls if paths given in the command line, boot │ │ │ scripts, and the code server should be interpreted as is strict or relaxed.

        OTP 26 and earlier defaults to relaxed, which means -pa myapp/ebin would │ │ │ @@ -231,18 +231,18 @@ │ │ │ " │ │ │ String Content │ │ │ " │ │ │ %% │ │ │ %% In OTP 27 it is instead interpreted as a │ │ │ %% Triple-Quoted String equivalent to │ │ │ "String Content"

        """"
        │ │ │ -++ foo() ++
        │ │ │ +++ foo() ++
        │ │ │  """"
        │ │ │  %% Became
        │ │ │ -"" ++ foo() ++ ""
        │ │ │ +"" ++ foo() ++ ""
        │ │ │  %%
        │ │ │  %% In OTP 27 it is instead interpreted as a
        │ │ │  %% Triple-Quoted String (triple-or-more) equivalent to
        │ │ │  "++ foo() ++"

        From Erlang/OTP 26.1 up to 27.0 the compiler issues a warning for a sequence of │ │ │ 3 or more double-quote characters since that is almost certainly a mistake or │ │ │ something like a result of bad automatic code generation. If a users gets that │ │ │ warning, the code should be corrected for example by inserting appropriate │ │ ├── ./usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/.build │ │ │ @@ -42,15 +42,15 @@ │ │ │ dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ dist/remixicon-NKANDIL5.woff2 │ │ │ -dist/search_data-8B3C849F.js │ │ │ +dist/search_data-FC9EDC58.js │ │ │ dist/sidebar_items-E25F4A7E.js │ │ │ driver.html │ │ │ driver_entry.html │ │ │ epmd_cmd.html │ │ │ erl_cmd.html │ │ │ erl_dist_protocol.html │ │ │ erl_driver.html │ │ ├── ./usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/alt_dist.html │ │ │ @@ -237,50 +237,50 @@ │ │ │ uds_dist example using a port driver written in C, erl_uds_dist is written │ │ │ entirely in Erlang.

        │ │ │ │ │ │ │ │ │ │ │ │ Exported Callback Functions │ │ │

        │ │ │ -

        The following functions are mandatory:

        • listen(Name) ->
          │ │ │ -  {ok, {Listen, Address, Creation}} | {error, Error}
          │ │ │ -listen(Name,Host) ->
          │ │ │ -  {ok, {Listen, Address, Creation}} | {error, Error}

          listen/2 is called once in order to listen for incoming connection requests. │ │ │ +

          The following functions are mandatory:

          • listen(Name) ->
            │ │ │ +  {ok, {Listen, Address, Creation}} | {error, Error}
            │ │ │ +listen(Name,Host) ->
            │ │ │ +  {ok, {Listen, Address, Creation}} | {error, Error}

            listen/2 is called once in order to listen for incoming connection requests. │ │ │ The call is made when the distribution is brought up. The argument Name is │ │ │ the part of the node name before the @ sign in the full node name. It can be │ │ │ either an atom or a string. The argument Host is the part of the node name │ │ │ after the @ sign in the full node name. It is always a string.

            The return value consists of a Listen handle (which is later passed to the │ │ │ accept/1 callback), Address which is a │ │ │ #net_address{} record with information about the address for the node (the │ │ │ #net_address{} record is defined in kernel/include/net_address.hrl), and │ │ │ Creation which (currently) is an integer 1, 2, or 3.

            If epmd is to be used for node discovery, you typically want │ │ │ to use the erl_epmd module (part of the kernel application) in order to │ │ │ -register the listen port with epmd and retrieve Creation to use.

          • address() ->
            │ │ │ +register the listen port with epmd and retrieve Creation to use.

          • address() ->
            │ │ │    Address

            address/0 is called in order to get the Address part of the │ │ │ listen/2 function without creating a listen socket. │ │ │ -All fields except address have to be set in the returned record

            Example:

            address() ->
            │ │ │ -    {ok, Host} = inet:gethostname(),
            │ │ │ -    #net_address{ host = Host, protocol = tcp, family = inet6 }.
          • accept(Listen) ->
            │ │ │ +All fields except address have to be set in the returned record

            Example:

            address() ->
            │ │ │ +    {ok, Host} = inet:gethostname(),
            │ │ │ +    #net_address{ host = Host, protocol = tcp, family = inet6 }.
          • accept(Listen) ->
            │ │ │    AcceptorPid

            accept/1 should spawn a process that accepts connections. This process │ │ │ should preferably execute on max priority. The process identifier of this │ │ │ process should be returned.

            The Listen argument will be the same as the Listen handle part of the │ │ │ return value of the listen/1 callback above. │ │ │ accept/1 is called only once when the distribution protocol is started.

            The caller of this function is a representative for net_kernel (this may or │ │ │ may not be the process registered as net_kernel) and is in this document │ │ │ identified as Kernel. When a connection has been accepted by the acceptor │ │ │ process, it needs to inform Kernel about the accepted connection. This is │ │ │ -done by passing a message on the form:

            Kernel ! {accept, AcceptorPid, DistController, Family, Proto}

            DistController is either the process or port identifier of the distribution │ │ │ +done by passing a message on the form:

            Kernel ! {accept, AcceptorPid, DistController, Family, Proto}

            DistController is either the process or port identifier of the distribution │ │ │ controller for the connection. The distribution controller should be created │ │ │ by the acceptor processes when a new connection is accepted. Its job is to │ │ │ dispatch traffic on the connection.

            Kernel responds with one of the following messages:

            • {Kernel, controller, SupervisorPid} - The request was accepted and │ │ │ SupervisorPid is the process identifier of the connection supervisor │ │ │ process (which is created in the │ │ │ accept_connection/5 callback).

            • {Kernel, unsupported_protocol} - The request was rejected. This is a │ │ │ fatal error. The acceptor process should terminate.

            When an accept sequence has been completed the acceptor process is expected to │ │ │ -continue accepting further requests.

          • accept_connection(AcceptorPid, DistCtrl, MyNode, Allowed, SetupTime) ->
            │ │ │ +continue accepting further requests.

          • accept_connection(AcceptorPid, DistCtrl, MyNode, Allowed, SetupTime) ->
            │ │ │    ConnectionSupervisorPid

            accept_connection/5 should spawn a process that will perform the Erlang │ │ │ distribution handshake for the connection. If the handshake successfully │ │ │ completes it should continue to function as a connection supervisor. This │ │ │ process should preferably execute on max priority and should be linked to │ │ │ the caller. The dist_util:net_ticker_spawn_options() function can be called │ │ │ to get spawn options suitable for this process which can be passed directly to │ │ │ erlang:spawn_opt/4. dist_util:net_ticker_spawn_options() will by default │ │ │ @@ -294,15 +294,15 @@ │ │ │ dist_util:handshake_other_started(HsData).

          • Allowed - To be passed along to │ │ │ dist_util:handshake_other_started(HsData).

          • SetupTime - Time used for creating a setup timer by a call to │ │ │ dist_util:start_timer(SetupTime). The timer should be passed along to │ │ │ dist_util:handshake_other_started(HsData).

          The created process should provide callbacks and other information needed for │ │ │ the handshake in a #hs_data{} record and call │ │ │ dist_util:handshake_other_started(HsData) with this record.

          dist_util:handshake_other_started(HsData) will perform the handshake and if │ │ │ the handshake successfully completes this process will then continue in a │ │ │ -connection supervisor loop as long as the connection is up.

        • setup(Node, Type, MyNode, LongOrShortNames, SetupTime) ->
          │ │ │ +connection supervisor loop as long as the connection is up.

        • setup(Node, Type, MyNode, LongOrShortNames, SetupTime) ->
          │ │ │    ConnectionSupervisorPid

          setup/5 should spawn a process that connects to Node. When connection has │ │ │ been established it should perform the Erlang distribution handshake for the │ │ │ connection. If the handshake successfully completes it should continue to │ │ │ function as a connection supervisor. This process should preferably execute on │ │ │ max priority and should be linked to the caller. The │ │ │ dist_util:net_ticker_spawn_options() function can be called to get spawn │ │ │ options suitable for this process which can be passed directly to │ │ │ @@ -320,23 +320,23 @@ │ │ │ may not be the process registered as net_kernel) and is in this document │ │ │ identified as Kernel.

          This function should, besides spawning the connection supervisor, also create │ │ │ a distribution controller. The distribution controller is either a process or │ │ │ a port which is responsible for dispatching traffic.

          The created process should provide callbacks and other information needed for │ │ │ the handshake in a #hs_data{} record and call │ │ │ dist_util:handshake_we_started(HsData) with this record.

          dist_util:handshake_we_started(HsData) will perform the handshake and the │ │ │ handshake successfully completes this process will then continue in a │ │ │ -connection supervisor loop as long as the connection is up.

        • close(Listen) ->
          │ │ │ -  void()

          Called in order to close the Listen handle that originally was passed from │ │ │ -the listen/1 callback.

        • select(NodeName) ->
          │ │ │ -  boolean()

          Return true if the host name part of the NodeName is valid for use with │ │ │ -this protocol; otherwise, false.

        There are also two optional functions that may be exported:

        • setopts(Listen, Opts) ->
          │ │ │ -  ok | {error, Error}

          The argument Listen is the handle originally passed from the │ │ │ +connection supervisor loop as long as the connection is up.

        • close(Listen) ->
          │ │ │ +  void()

          Called in order to close the Listen handle that originally was passed from │ │ │ +the listen/1 callback.

        • select(NodeName) ->
          │ │ │ +  boolean()

          Return true if the host name part of the NodeName is valid for use with │ │ │ +this protocol; otherwise, false.

        There are also two optional functions that may be exported:

        • setopts(Listen, Opts) ->
          │ │ │ +  ok | {error, Error}

          The argument Listen is the handle originally passed from the │ │ │ listen/1 callback. The argument Opts is a list of │ │ │ -options to set on future connections.

        • getopts(Listen, Opts) ->
          │ │ │ -  {ok, OptionValues} | {error, Error}

          The argument Listen is the handle originally passed from the │ │ │ +options to set on future connections.

        • getopts(Listen, Opts) ->
          │ │ │ +  {ok, OptionValues} | {error, Error}

          The argument Listen is the handle originally passed from the │ │ │ listen/1 callback. The argument Opts is a list of │ │ │ options to read for future connections.

        │ │ │ │ │ │ │ │ │ │ │ │ The #hs_data{} Record │ │ │

        │ │ │ @@ -350,44 +350,44 @@ │ │ │ accept_connection/5.

      • other_node - Name of the other node. This field │ │ │ is only mandatory when this node initiates the connection. That is, when │ │ │ connection is set up via setup/5.

      • this_node - The node name of this node.

      • socket - The identifier of the distribution │ │ │ controller.

      • timer - The timer created using │ │ │ dist_util:start_timer/1.

      • allowed - Information passed as Allowed to │ │ │ accept_connection/5. This field is only mandatory when the remote node │ │ │ initiated the connection. That is, when the connection is set up via │ │ │ -accept_connection/5.

      • f_send - A fun with the following signature:

        fun (DistCtrlr, Data) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller and Data │ │ │ -is io data to pass to the other side.

        Only used during handshake phase.

      • f_recv - A fun with the following signature:

        fun (DistCtrlr, Length) -> {ok, Packet} | {error, Reason}

        where DistCtrlr is the identifier of the distribution controller. If │ │ │ +accept_connection/5.

      • f_send - A fun with the following signature:

        fun (DistCtrlr, Data) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller and Data │ │ │ +is io data to pass to the other side.

        Only used during handshake phase.

      • f_recv - A fun with the following signature:

        fun (DistCtrlr, Length) -> {ok, Packet} | {error, Reason}

        where DistCtrlr is the identifier of the distribution controller. If │ │ │ Length is 0, all available bytes should be returned. If Length > 0, │ │ │ exactly Length bytes should be returned, or an error; possibly discarding │ │ │ less than Length bytes of data when the connection is closed from the other │ │ │ side. It is used for passive receive of data from the other end.

        Only used during handshake phase.

      • f_setopts_pre_nodeup - A fun with the │ │ │ -following signature:

        fun (DistCtrlr) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller. Called │ │ │ +following signature:

        fun (DistCtrlr) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller. Called │ │ │ just before the distribution channel is taken up for normal traffic.

        Only used during handshake phase.

      • f_setopts_post_nodeup - A fun with │ │ │ -the following signature:

        fun (DistCtrlr) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller. Called │ │ │ -just after distribution channel has been taken up for normal traffic.

        Only used during handshake phase.

      • f_getll - A fun with the following signature:

        fun (DistCtrlr) -> ID

        where DistCtrlr is the identifier of the distribution controller and ID is │ │ │ +the following signature:

        fun (DistCtrlr) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller. Called │ │ │ +just after distribution channel has been taken up for normal traffic.

        Only used during handshake phase.

      • f_getll - A fun with the following signature:

        fun (DistCtrlr) -> ID

        where DistCtrlr is the identifier of the distribution controller and ID is │ │ │ the identifier of the low level entity that handles the connection (often │ │ │ -DistCtrlr itself).

        Only used during handshake phase.

      • f_address - A fun with the following signature:

        fun (DistCtrlr, Node) -> NetAddress

        where DistCtrlr is the identifier of the distribution controller, Node is │ │ │ +DistCtrlr itself).

        Only used during handshake phase.

      • f_address - A fun with the following signature:

        fun (DistCtrlr, Node) -> NetAddress

        where DistCtrlr is the identifier of the distribution controller, Node is │ │ │ the node name of the node on the other end, and NetAddress is a │ │ │ #net_address{} record with information about the address for the Node on │ │ │ the other end of the connection. The #net_address{} record is defined in │ │ │ -kernel/include/net_address.hrl.

        Only used during handshake phase.

      • mf_tick - A fun with the following signature:

        fun (DistCtrlr) -> void()

        where DistCtrlr is the identifier of the distribution controller. This │ │ │ +kernel/include/net_address.hrl.

        Only used during handshake phase.

      • mf_tick - A fun with the following signature:

        fun (DistCtrlr) -> void()

        where DistCtrlr is the identifier of the distribution controller. This │ │ │ function should send information over the connection that is not interpreted │ │ │ by the other end while increasing the statistics of received packets on the │ │ │ other end. This is usually implemented by sending an empty packet.

        Note

        It is of vital importance that this operation does not block the caller for │ │ │ -a long time. This since it is called from the connection supervisor.

        Used when connection is up.

      • mf_getstat - A fun with the following signature:

        fun (DistCtrlr) -> {ok, Received, Sent, PendSend}

        where DistCtrlr is the identifier of the distribution controller, Received │ │ │ +a long time. This since it is called from the connection supervisor.

        Used when connection is up.

      • mf_getstat - A fun with the following signature:

        fun (DistCtrlr) -> {ok, Received, Sent, PendSend}

        where DistCtrlr is the identifier of the distribution controller, Received │ │ │ is received packets, Sent is sent packets, and PendSend is amount of data │ │ │ in queue to be sent (typically in bytes, but dist_util only checks whether │ │ │ the value is non-zero to know there is data in queue) or a boolean/0 │ │ │ indicating whether there are packets in queue to be sent.

        Note

        It is of vital importance that this operation does not block the caller for │ │ │ a long time. This since it is called from the connection supervisor.

        Used when connection is up.

      • request_type - The request Type as passed to │ │ │ setup/5. This is only mandatory when the connection has │ │ │ -been initiated by this node. That is, the connection is set up via setup/5.

      • mf_setopts - A fun with the following signature:

        fun (DistCtrl, Opts) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller and Opts │ │ │ -is a list of options to set on the connection.

        This function is optional. Used when connection is up.

      • mf_getopts - A fun with the following signature:

        fun (DistCtrl, Opts) -> {ok, OptionValues} | {error, Error}

        where DistCtrlr is the identifier of the distribution controller and Opts │ │ │ +been initiated by this node. That is, the connection is set up via setup/5.

      • mf_setopts - A fun with the following signature:

        fun (DistCtrl, Opts) -> ok | {error, Error}

        where DistCtrlr is the identifier of the distribution controller and Opts │ │ │ +is a list of options to set on the connection.

        This function is optional. Used when connection is up.

      • mf_getopts - A fun with the following signature:

        fun (DistCtrl, Opts) -> {ok, OptionValues} | {error, Error}

        where DistCtrlr is the identifier of the distribution controller and Opts │ │ │ is a list of options to read for the connection.

        This function is optional. Used when connection is up.

      • f_handshake_complete - A fun with the │ │ │ -following signature:

        fun (DistCtrlr, Node, DHandle) -> void()

        where DistCtrlr is the identifier of the distribution controller, Node is │ │ │ +following signature:

        fun (DistCtrlr, Node, DHandle) -> void()

        where DistCtrlr is the identifier of the distribution controller, Node is │ │ │ the node name of the node connected at the other end, and DHandle is a │ │ │ distribution handle needed by a distribution controller process when calling │ │ │ the following BIFs:

        This function is called when the handshake has completed and the distribution │ │ │ channel is up. The distribution controller can begin dispatching traffic over │ │ │ the channel. This function is optional.

        Only used during handshake phase.

      • add_flags - │ │ │ Distribution flags to add to the connection. │ │ │ Currently all (non obsolete) flags will automatically be enabled.

        This flag field is optional.

      • reject_flags - │ │ │ @@ -515,70 +515,70 @@ │ │ │ is needed. We will use the simple method of having a header of four bytes │ │ │ containing the length of the package in a big-endian 32-bit integer. As Unix │ │ │ domain sockets only can be used between processes on the same machine, we do not │ │ │ need to code the integer in some special endianness, but we will do it anyway │ │ │ because in most situation you need to do it. Unix domain sockets are reliable │ │ │ and order maintaining, so we do not need to implement resends and such in the │ │ │ driver.

        We start writing the example Unix domain sockets driver by declaring prototypes │ │ │ -and filling in a static ErlDrvEntry structure:

        ( 1) #include <stdio.h>
        │ │ │ -( 2) #include <stdlib.h>
        │ │ │ -( 3) #include <string.h>
        │ │ │ -( 4) #include <unistd.h>
        │ │ │ -( 5) #include <errno.h>
        │ │ │ -( 6) #include <sys/types.h>
        │ │ │ -( 7) #include <sys/stat.h>
        │ │ │ -( 8) #include <sys/socket.h>
        │ │ │ -( 9) #include <sys/un.h>
        │ │ │ -(10) #include <fcntl.h>
        │ │ │ +and filling in a static ErlDrvEntry structure:

        ( 1) #include <stdio.h>
        │ │ │ +( 2) #include <stdlib.h>
        │ │ │ +( 3) #include <string.h>
        │ │ │ +( 4) #include <unistd.h>
        │ │ │ +( 5) #include <errno.h>
        │ │ │ +( 6) #include <sys/types.h>
        │ │ │ +( 7) #include <sys/stat.h>
        │ │ │ +( 8) #include <sys/socket.h>
        │ │ │ +( 9) #include <sys/un.h>
        │ │ │ +(10) #include <fcntl.h>
        │ │ │  
        │ │ │ -(11) #define HAVE_UIO_H
        │ │ │ -(12) #include "erl_driver.h"
        │ │ │ +(11) #define HAVE_UIO_H
        │ │ │ +(12) #include "erl_driver.h"
        │ │ │  
        │ │ │ -(13) /*
        │ │ │ +(13) /*
        │ │ │  (14) ** Interface routines
        │ │ │  (15) */
        │ │ │ -(16) static ErlDrvData uds_start(ErlDrvPort port, char *buff);
        │ │ │ -(17) static void uds_stop(ErlDrvData handle);
        │ │ │ -(18) static void uds_command(ErlDrvData handle, char *buff, int bufflen);
        │ │ │ -(19) static void uds_input(ErlDrvData handle, ErlDrvEvent event);
        │ │ │ -(20) static void uds_output(ErlDrvData handle, ErlDrvEvent event);
        │ │ │ -(21) static void uds_finish(void);
        │ │ │ -(22) static int uds_control(ErlDrvData handle, unsigned int command,
        │ │ │ -(23)                        char* buf, int count, char** res, int res_size);
        │ │ │ -
        │ │ │ -(24) /* The driver entry */
        │ │ │ -(25) static ErlDrvEntry uds_driver_entry = {
        │ │ │ -(26)     NULL,                            /* init, N/A */
        │ │ │ -(27)     uds_start,                       /* start, called when port is opened */
        │ │ │ -(28)     uds_stop,                        /* stop, called when port is closed */
        │ │ │ -(29)     uds_command,                     /* output, called when erlang has sent */
        │ │ │ -(30)     uds_input,                       /* ready_input, called when input
        │ │ │ +(16) static ErlDrvData uds_start(ErlDrvPort port, char *buff);
        │ │ │ +(17) static void uds_stop(ErlDrvData handle);
        │ │ │ +(18) static void uds_command(ErlDrvData handle, char *buff, int bufflen);
        │ │ │ +(19) static void uds_input(ErlDrvData handle, ErlDrvEvent event);
        │ │ │ +(20) static void uds_output(ErlDrvData handle, ErlDrvEvent event);
        │ │ │ +(21) static void uds_finish(void);
        │ │ │ +(22) static int uds_control(ErlDrvData handle, unsigned int command,
        │ │ │ +(23)                        char* buf, int count, char** res, int res_size);
        │ │ │ +
        │ │ │ +(24) /* The driver entry */
        │ │ │ +(25) static ErlDrvEntry uds_driver_entry = {
        │ │ │ +(26)     NULL,                            /* init, N/A */
        │ │ │ +(27)     uds_start,                       /* start, called when port is opened */
        │ │ │ +(28)     uds_stop,                        /* stop, called when port is closed */
        │ │ │ +(29)     uds_command,                     /* output, called when erlang has sent */
        │ │ │ +(30)     uds_input,                       /* ready_input, called when input
        │ │ │  (31)                                         descriptor ready */
        │ │ │ -(32)     uds_output,                      /* ready_output, called when output
        │ │ │ +(32)     uds_output,                      /* ready_output, called when output
        │ │ │  (33)                                         descriptor ready */
        │ │ │ -(34)     "uds_drv",                       /* char *driver_name, the argument
        │ │ │ +(34)     "uds_drv",                       /* char *driver_name, the argument
        │ │ │  (35)                                         to open_port */
        │ │ │ -(36)     uds_finish,                      /* finish, called when unloaded */
        │ │ │ -(37)     NULL,                            /* void * that is not used (BC) */
        │ │ │ -(38)     uds_control,                     /* control, port_control callback */
        │ │ │ -(39)     NULL,                            /* timeout, called on timeouts */
        │ │ │ -(40)     NULL,                            /* outputv, vector output interface */
        │ │ │ -(41)     NULL,                            /* ready_async callback */
        │ │ │ -(42)     NULL,                            /* flush callback */
        │ │ │ -(43)     NULL,                            /* call callback */
        │ │ │ -(44)     NULL,                            /* event callback */
        │ │ │ -(45)     ERL_DRV_EXTENDED_MARKER,         /* Extended driver interface marker */
        │ │ │ -(46)     ERL_DRV_EXTENDED_MAJOR_VERSION,  /* Major version number */
        │ │ │ -(47)     ERL_DRV_EXTENDED_MINOR_VERSION,  /* Minor version number */
        │ │ │ -(48)     ERL_DRV_FLAG_SOFT_BUSY,          /* Driver flags. Soft busy flag is
        │ │ │ +(36)     uds_finish,                      /* finish, called when unloaded */
        │ │ │ +(37)     NULL,                            /* void * that is not used (BC) */
        │ │ │ +(38)     uds_control,                     /* control, port_control callback */
        │ │ │ +(39)     NULL,                            /* timeout, called on timeouts */
        │ │ │ +(40)     NULL,                            /* outputv, vector output interface */
        │ │ │ +(41)     NULL,                            /* ready_async callback */
        │ │ │ +(42)     NULL,                            /* flush callback */
        │ │ │ +(43)     NULL,                            /* call callback */
        │ │ │ +(44)     NULL,                            /* event callback */
        │ │ │ +(45)     ERL_DRV_EXTENDED_MARKER,         /* Extended driver interface marker */
        │ │ │ +(46)     ERL_DRV_EXTENDED_MAJOR_VERSION,  /* Major version number */
        │ │ │ +(47)     ERL_DRV_EXTENDED_MINOR_VERSION,  /* Minor version number */
        │ │ │ +(48)     ERL_DRV_FLAG_SOFT_BUSY,          /* Driver flags. Soft busy flag is
        │ │ │  (49)                                         required for distribution drivers */
        │ │ │ -(50)     NULL,                            /* Reserved for internal use */
        │ │ │ -(51)     NULL,                            /* process_exit callback */
        │ │ │ -(52)     NULL                             /* stop_select callback */
        │ │ │ -(53) };

        On line 1-10 the OS headers needed for the driver are included. As this driver │ │ │ +(50) NULL, /* Reserved for internal use */ │ │ │ +(51) NULL, /* process_exit callback */ │ │ │ +(52) NULL /* stop_select callback */ │ │ │ +(53) };

        On line 1-10 the OS headers needed for the driver are included. As this driver │ │ │ is written for Solaris, we know that the header uio.h exists. So the │ │ │ preprocessor variable HAVE_UIO_H can be defined before erl_driver.h is │ │ │ included on line 12. The definition of HAVE_UIO_H will make the I/O vectors │ │ │ used in Erlang's driver queues to correspond to the operating systems ditto, │ │ │ which is very convenient.

        On line 16-23 the different callback functions are declared ("forward │ │ │ declarations").

        The driver structure is similar for statically linked-in drivers and dynamically │ │ │ loaded. However, some of the fields are to be left empty (that is, initialized │ │ │ @@ -621,25 +621,25 @@ │ │ │ data mode and all data is immediately read and passed further to the Erlang │ │ │ emulator. In data mode, no data arriving to uds_command is interpreted, only │ │ │ packaged and sent out on the socket. The uds_control callback does the │ │ │ switching between those two modes.

        While net_kernel informs different subsystems that the connection is coming │ │ │ up, the port is to accept data to send. However, the port should not receive any │ │ │ data, to avoid that data arrives from another node before every kernel subsystem │ │ │ is prepared to handle it. A third mode, named intermediate, is used for this │ │ │ -intermediate stage.

        An enum is defined for the different types of ports:

        ( 1) typedef enum {
        │ │ │ -( 2)     portTypeUnknown,      /* An uninitialized port */
        │ │ │ -( 3)     portTypeListener,     /* A listening port/socket */
        │ │ │ -( 4)     portTypeAcceptor,     /* An intermediate stage when accepting
        │ │ │ +intermediate stage.

        An enum is defined for the different types of ports:

        ( 1) typedef enum {
        │ │ │ +( 2)     portTypeUnknown,      /* An uninitialized port */
        │ │ │ +( 3)     portTypeListener,     /* A listening port/socket */
        │ │ │ +( 4)     portTypeAcceptor,     /* An intermediate stage when accepting
        │ │ │  ( 5)                              on a listen port */
        │ │ │ -( 6)     portTypeConnector,    /* An intermediate stage when connecting */
        │ │ │ -( 7)     portTypeCommand,      /* A connected open port in command mode */
        │ │ │ -( 8)     portTypeIntermediate, /* A connected open port in special
        │ │ │ +( 6)     portTypeConnector,    /* An intermediate stage when connecting */
        │ │ │ +( 7)     portTypeCommand,      /* A connected open port in command mode */
        │ │ │ +( 8)     portTypeIntermediate, /* A connected open port in special
        │ │ │  ( 9)                              half active mode */
        │ │ │ -(10)     portTypeData          /* A connected open port in data mode */
        │ │ │ -(11) } PortType;

        The different types are as follows:

        • portTypeUnknown - The type a port has when it is opened, but not bound │ │ │ +(10) portTypeData /* A connected open port in data mode */ │ │ │ +(11) } PortType;

        The different types are as follows:

        • portTypeUnknown - The type a port has when it is opened, but not bound │ │ │ to any file descriptor.

        • portTypeListener - A port that is connected to a listen socket. This │ │ │ port does not do much, no data pumping is done on this socket, but read data │ │ │ is available when one is trying to do an accept on the port.

        • portTypeAcceptor - This port is to represent the result of an accept │ │ │ operation. It is created when one wants to accept from a listen socket, and it │ │ │ is converted to a portTypeCommand when the accept succeeds.

        • portTypeConnector - Very similar to portTypeAcceptor, an intermediate │ │ │ stage between the request for a connect operation and that the socket is │ │ │ connected to an accepting ditto in the other end. When the sockets are │ │ │ @@ -647,37 +647,37 @@ │ │ │ mode mentioned earlier.

        • portTypeIntermediate - The intermediate stage for a connected socket. │ │ │ There is to be no processing of input for this socket.

        • portTypeData - The mode where data is pumped through the port and the │ │ │ uds_command routine regards every call as a call where sending is wanted. In │ │ │ this mode, all input available is read and sent to Erlang when it arrives on │ │ │ the socket, much like in the active mode of a gen_tcp socket.

        We study the state that is needed for the ports. Notice that not all fields are │ │ │ used for all types of ports. Some space could be saved by using unions, but that │ │ │ would clutter the code with multiple indirections, so here is used one struct │ │ │ -for all types of ports, for readability:

        ( 1) typedef unsigned char Byte;
        │ │ │ -( 2) typedef unsigned int Word;
        │ │ │ +for all types of ports, for readability:

        ( 1) typedef unsigned char Byte;
        │ │ │ +( 2) typedef unsigned int Word;
        │ │ │  
        │ │ │ -( 3) typedef struct uds_data {
        │ │ │ -( 4)     int fd;                   /* File descriptor */
        │ │ │ -( 5)     ErlDrvPort port;          /* The port identifier */
        │ │ │ -( 6)     int lockfd;               /* The file descriptor for a lock file in
        │ │ │ +( 3) typedef struct uds_data {
        │ │ │ +( 4)     int fd;                   /* File descriptor */
        │ │ │ +( 5)     ErlDrvPort port;          /* The port identifier */
        │ │ │ +( 6)     int lockfd;               /* The file descriptor for a lock file in
        │ │ │  ( 7)                                  case of listen sockets */
        │ │ │ -( 8)     Byte creation;            /* The creation serial derived from the
        │ │ │ +( 8)     Byte creation;            /* The creation serial derived from the
        │ │ │  ( 9)                                  lock file */
        │ │ │ -(10)     PortType type;            /* Type of port */
        │ │ │ -(11)     char *name;               /* Short name of socket for unlink */
        │ │ │ -(12)     Word sent;                /* Bytes sent */
        │ │ │ -(13)     Word received;            /* Bytes received */
        │ │ │ -(14)     struct uds_data *partner; /* The partner in an accept/listen pair */
        │ │ │ -(15)     struct uds_data *next;    /* Next structure in list */
        │ │ │ -(16)     /* The input buffer and its data */
        │ │ │ -(17)     int buffer_size;          /* The allocated size of the input buffer */
        │ │ │ -(18)     int buffer_pos;           /* Current position in input buffer */
        │ │ │ -(19)     int header_pos;           /* Where the current header is in the
        │ │ │ +(10)     PortType type;            /* Type of port */
        │ │ │ +(11)     char *name;               /* Short name of socket for unlink */
        │ │ │ +(12)     Word sent;                /* Bytes sent */
        │ │ │ +(13)     Word received;            /* Bytes received */
        │ │ │ +(14)     struct uds_data *partner; /* The partner in an accept/listen pair */
        │ │ │ +(15)     struct uds_data *next;    /* Next structure in list */
        │ │ │ +(16)     /* The input buffer and its data */
        │ │ │ +(17)     int buffer_size;          /* The allocated size of the input buffer */
        │ │ │ +(18)     int buffer_pos;           /* Current position in input buffer */
        │ │ │ +(19)     int header_pos;           /* Where the current header is in the
        │ │ │  (20)                                  input buffer */
        │ │ │ -(21)     Byte *buffer;             /* The actual input buffer */
        │ │ │ -(22) } UdsData;

        This structure is used for all types of ports although some fields are useless │ │ │ +(21) Byte *buffer; /* The actual input buffer */ │ │ │ +(22) } UdsData;

        This structure is used for all types of ports although some fields are useless │ │ │ for some types. The least memory consuming solution would be to arrange this │ │ │ structure as a union of structures. However, the multiple indirections in the │ │ │ code to access a field in such a structure would clutter the code too much for │ │ │ an example.

        The fields in the structure are as follows:

        • fd - The file descriptor of the socket associated with the port.

        • port - The port identifier for the port that this structure corresponds │ │ │ to. It is needed for most driver_XXX calls from the driver back to the │ │ │ emulator.

        • lockfd - If the socket is a listen socket, we use a separate (regular) │ │ │ file for two purposes:

          • We want a locking mechanism that gives no race conditions, to be sure if │ │ │ @@ -718,105 +718,105 @@ │ │ │ details about buffering and other things unrelated to driver writing are not │ │ │ explained. Likewise are some peculiarities of the UDS protocol not explained in │ │ │ detail. The chosen protocol is not important.

            Prototypes for the driver callback routines can be found in the erl_driver.h │ │ │ header file.

            The driver initialization routine is (usually) declared with a macro to make the │ │ │ driver easier to port between different operating systems (and flavors of │ │ │ systems). This is the only routine that must have a well-defined name. All other │ │ │ callbacks are reached through the driver structure. The macro to use is named │ │ │ -DRIVER_INIT and takes the driver name as parameter:

            (1) /* Beginning of linked list of ports */
            │ │ │ -(2) static UdsData *first_data;
            │ │ │ +DRIVER_INIT and takes the driver name as parameter:

            (1) /* Beginning of linked list of ports */
            │ │ │ +(2) static UdsData *first_data;
            │ │ │  
            │ │ │ -(3) DRIVER_INIT(uds_drv)
            │ │ │ -(4) {
            │ │ │ -(5)     first_data = NULL;
            │ │ │ -(6)     return &uds_driver_entry;
            │ │ │ -(7) }

            The routine initializes the single global data structure and returns a pointer │ │ │ +(3) DRIVER_INIT(uds_drv) │ │ │ +(4) { │ │ │ +(5) first_data = NULL; │ │ │ +(6) return &uds_driver_entry; │ │ │ +(7) }

            The routine initializes the single global data structure and returns a pointer │ │ │ to the driver entry. The routine is called when erl_ddll:load_driver is called │ │ │ from Erlang.

            The uds_start routine is called when a port is opened from Erlang. In this │ │ │ case, we only allocate a structure and initialize it. Creating the actual socket │ │ │ -is left to the uds_command routine.

            ( 1) static ErlDrvData uds_start(ErlDrvPort port, char *buff)
            │ │ │ -( 2) {
            │ │ │ -( 3)     UdsData *ud;
            │ │ │ -( 4)
            │ │ │ -( 5)     ud = ALLOC(sizeof(UdsData));
            │ │ │ -( 6)     ud->fd = -1;
            │ │ │ -( 7)     ud->lockfd = -1;
            │ │ │ -( 8)     ud->creation = 0;
            │ │ │ -( 9)     ud->port = port;
            │ │ │ -(10)     ud->type = portTypeUnknown;
            │ │ │ -(11)     ud->name = NULL;
            │ │ │ -(12)     ud->buffer_size = 0;
            │ │ │ -(13)     ud->buffer_pos = 0;
            │ │ │ -(14)     ud->header_pos = 0;
            │ │ │ -(15)     ud->buffer = NULL;
            │ │ │ -(16)     ud->sent = 0;
            │ │ │ -(17)     ud->received = 0;
            │ │ │ -(18)     ud->partner = NULL;
            │ │ │ -(19)     ud->next = first_data;
            │ │ │ -(20)     first_data = ud;
            │ │ │ -(21)
            │ │ │ -(22)     return((ErlDrvData) ud);
            │ │ │ -(23) }

            Every data item is initialized, so that no problems arise when a newly created │ │ │ +is left to the uds_command routine.

            ( 1) static ErlDrvData uds_start(ErlDrvPort port, char *buff)
            │ │ │ +( 2) {
            │ │ │ +( 3)     UdsData *ud;
            │ │ │ +( 4)
            │ │ │ +( 5)     ud = ALLOC(sizeof(UdsData));
            │ │ │ +( 6)     ud->fd = -1;
            │ │ │ +( 7)     ud->lockfd = -1;
            │ │ │ +( 8)     ud->creation = 0;
            │ │ │ +( 9)     ud->port = port;
            │ │ │ +(10)     ud->type = portTypeUnknown;
            │ │ │ +(11)     ud->name = NULL;
            │ │ │ +(12)     ud->buffer_size = 0;
            │ │ │ +(13)     ud->buffer_pos = 0;
            │ │ │ +(14)     ud->header_pos = 0;
            │ │ │ +(15)     ud->buffer = NULL;
            │ │ │ +(16)     ud->sent = 0;
            │ │ │ +(17)     ud->received = 0;
            │ │ │ +(18)     ud->partner = NULL;
            │ │ │ +(19)     ud->next = first_data;
            │ │ │ +(20)     first_data = ud;
            │ │ │ +(21)
            │ │ │ +(22)     return((ErlDrvData) ud);
            │ │ │ +(23) }

            Every data item is initialized, so that no problems arise when a newly created │ │ │ port is closed (without there being any corresponding socket). This routine is │ │ │ called when open_port({spawn, "uds_drv"},[]) is called from │ │ │ Erlang.

            The uds_command routine is the routine called when an Erlang process sends │ │ │ data to the port. This routine handles all asynchronous commands when the port │ │ │ is in command mode and the sending of all data when the port is in data │ │ │ -mode:

            ( 1) static void uds_command(ErlDrvData handle, char *buff, int bufflen)
            │ │ │ -( 2) {
            │ │ │ -( 3)     UdsData *ud = (UdsData *) handle;
            │ │ │ -
            │ │ │ -( 4)     if (ud->type == portTypeData || ud->type == portTypeIntermediate) {
            │ │ │ -( 5)         DEBUGF(("Passive do_send %d",bufflen));
            │ │ │ -( 6)         do_send(ud, buff + 1, bufflen - 1); /* XXX */
            │ │ │ -( 7)         return;
            │ │ │ -( 8)     }
            │ │ │ -( 9)     if (bufflen == 0) {
            │ │ │ -(10)         return;
            │ │ │ -(11)     }
            │ │ │ -(12)     switch (*buff) {
            │ │ │ -(13)     case 'L':
            │ │ │ -(14)         if (ud->type != portTypeUnknown) {
            │ │ │ -(15)             driver_failure_posix(ud->port, ENOTSUP);
            │ │ │ -(16)             return;
            │ │ │ -(17)         }
            │ │ │ -(18)         uds_command_listen(ud,buff,bufflen);
            │ │ │ -(19)         return;
            │ │ │ -(20)     case 'A':
            │ │ │ -(21)         if (ud->type != portTypeUnknown) {
            │ │ │ -(22)             driver_failure_posix(ud->port, ENOTSUP);
            │ │ │ -(23)             return;
            │ │ │ -(24)         }
            │ │ │ -(25)         uds_command_accept(ud,buff,bufflen);
            │ │ │ -(26)         return;
            │ │ │ -(27)     case 'C':
            │ │ │ -(28)         if (ud->type != portTypeUnknown) {
            │ │ │ -(29)             driver_failure_posix(ud->port, ENOTSUP);
            │ │ │ -(30)             return;
            │ │ │ -(31)         }
            │ │ │ -(32)         uds_command_connect(ud,buff,bufflen);
            │ │ │ -(33)         return;
            │ │ │ -(34)     case 'S':
            │ │ │ -(35)         if (ud->type != portTypeCommand) {
            │ │ │ -(36)             driver_failure_posix(ud->port, ENOTSUP);
            │ │ │ -(37)             return;
            │ │ │ -(38)         }
            │ │ │ -(39)         do_send(ud, buff + 1, bufflen - 1);
            │ │ │ -(40)         return;
            │ │ │ -(41)     case 'R':
            │ │ │ -(42)         if (ud->type != portTypeCommand) {
            │ │ │ -(43)             driver_failure_posix(ud->port, ENOTSUP);
            │ │ │ -(44)             return;
            │ │ │ -(45)         }
            │ │ │ -(46)         do_recv(ud);
            │ │ │ -(47)         return;
            │ │ │ -(48)     default:
            │ │ │ -(49)         return;
            │ │ │ -(50)     }
            │ │ │ -(51) }

            The command routine takes three parameters; the handle returned for the port by │ │ │ +mode:

            ( 1) static void uds_command(ErlDrvData handle, char *buff, int bufflen)
            │ │ │ +( 2) {
            │ │ │ +( 3)     UdsData *ud = (UdsData *) handle;
            │ │ │ +
            │ │ │ +( 4)     if (ud->type == portTypeData || ud->type == portTypeIntermediate) {
            │ │ │ +( 5)         DEBUGF(("Passive do_send %d",bufflen));
            │ │ │ +( 6)         do_send(ud, buff + 1, bufflen - 1); /* XXX */
            │ │ │ +( 7)         return;
            │ │ │ +( 8)     }
            │ │ │ +( 9)     if (bufflen == 0) {
            │ │ │ +(10)         return;
            │ │ │ +(11)     }
            │ │ │ +(12)     switch (*buff) {
            │ │ │ +(13)     case 'L':
            │ │ │ +(14)         if (ud->type != portTypeUnknown) {
            │ │ │ +(15)             driver_failure_posix(ud->port, ENOTSUP);
            │ │ │ +(16)             return;
            │ │ │ +(17)         }
            │ │ │ +(18)         uds_command_listen(ud,buff,bufflen);
            │ │ │ +(19)         return;
            │ │ │ +(20)     case 'A':
            │ │ │ +(21)         if (ud->type != portTypeUnknown) {
            │ │ │ +(22)             driver_failure_posix(ud->port, ENOTSUP);
            │ │ │ +(23)             return;
            │ │ │ +(24)         }
            │ │ │ +(25)         uds_command_accept(ud,buff,bufflen);
            │ │ │ +(26)         return;
            │ │ │ +(27)     case 'C':
            │ │ │ +(28)         if (ud->type != portTypeUnknown) {
            │ │ │ +(29)             driver_failure_posix(ud->port, ENOTSUP);
            │ │ │ +(30)             return;
            │ │ │ +(31)         }
            │ │ │ +(32)         uds_command_connect(ud,buff,bufflen);
            │ │ │ +(33)         return;
            │ │ │ +(34)     case 'S':
            │ │ │ +(35)         if (ud->type != portTypeCommand) {
            │ │ │ +(36)             driver_failure_posix(ud->port, ENOTSUP);
            │ │ │ +(37)             return;
            │ │ │ +(38)         }
            │ │ │ +(39)         do_send(ud, buff + 1, bufflen - 1);
            │ │ │ +(40)         return;
            │ │ │ +(41)     case 'R':
            │ │ │ +(42)         if (ud->type != portTypeCommand) {
            │ │ │ +(43)             driver_failure_posix(ud->port, ENOTSUP);
            │ │ │ +(44)             return;
            │ │ │ +(45)         }
            │ │ │ +(46)         do_recv(ud);
            │ │ │ +(47)         return;
            │ │ │ +(48)     default:
            │ │ │ +(49)         return;
            │ │ │ +(50)     }
            │ │ │ +(51) }

            The command routine takes three parameters; the handle returned for the port by │ │ │ uds_start, which is a pointer to the internal port structure, the data buffer, │ │ │ and the length of the data buffer. The buffer is the data sent from Erlang (a │ │ │ list of bytes) converted to an C array (of bytes).

            If Erlang sends, for example, the list [$a,$b,$c] to the port, the bufflen │ │ │ variable is 3 and the buff variable contains {'a','b','c'} (no NULL │ │ │ termination). Usually the first byte is used as an opcode, which is the case in │ │ │ this driver too (at least when the port is in command mode). The opcodes are │ │ │ defined as follows:

            • 'L'<socket name> - Creates and listens on socket with the specified │ │ │ @@ -837,127 +837,127 @@ │ │ │ example, line 15). Notice that the failure routines make a call to the │ │ │ uds_stop routine, which will remove the internal port data. The handle (and │ │ │ the casted handle ud) is therefore invalid pointers after a driver_failure │ │ │ call and we should return immediately. The runtime system will send exit │ │ │ signals to all linked processes.

              The uds_input routine is called when data is available on a file descriptor │ │ │ previously passed to the driver_select routine. This occurs typically when a │ │ │ read command is issued and no data is available. The do_recv routine is as │ │ │ -follows:

              ( 1) static void do_recv(UdsData *ud)
              │ │ │ -( 2) {
              │ │ │ -( 3)     int res;
              │ │ │ -( 4)     char *ibuf;
              │ │ │ -( 5)     for(;;) {
              │ │ │ -( 6)         if ((res = buffered_read_package(ud,&ibuf)) < 0) {
              │ │ │ -( 7)             if (res == NORMAL_READ_FAILURE) {
              │ │ │ -( 8)                 driver_select(ud->port, (ErlDrvEvent) ud->fd, DO_READ, 1);
              │ │ │ -( 9)             } else {
              │ │ │ -(10)                 driver_failure_eof(ud->port);
              │ │ │ -(11)             }
              │ │ │ -(12)             return;
              │ │ │ -(13)         }
              │ │ │ -(14)         /* Got a package */
              │ │ │ -(15)         if (ud->type == portTypeCommand) {
              │ │ │ -(16)             ibuf[-1] = 'R'; /* There is always room for a single byte
              │ │ │ +follows:

              ( 1) static void do_recv(UdsData *ud)
              │ │ │ +( 2) {
              │ │ │ +( 3)     int res;
              │ │ │ +( 4)     char *ibuf;
              │ │ │ +( 5)     for(;;) {
              │ │ │ +( 6)         if ((res = buffered_read_package(ud,&ibuf)) < 0) {
              │ │ │ +( 7)             if (res == NORMAL_READ_FAILURE) {
              │ │ │ +( 8)                 driver_select(ud->port, (ErlDrvEvent) ud->fd, DO_READ, 1);
              │ │ │ +( 9)             } else {
              │ │ │ +(10)                 driver_failure_eof(ud->port);
              │ │ │ +(11)             }
              │ │ │ +(12)             return;
              │ │ │ +(13)         }
              │ │ │ +(14)         /* Got a package */
              │ │ │ +(15)         if (ud->type == portTypeCommand) {
              │ │ │ +(16)             ibuf[-1] = 'R'; /* There is always room for a single byte
              │ │ │  (17)                                opcode before the actual buffer
              │ │ │  (18)                                (where the packet header was) */
              │ │ │ -(19)             driver_output(ud->port,ibuf - 1, res + 1);
              │ │ │ -(20)             driver_select(ud->port, (ErlDrvEvent) ud->fd, DO_READ,0);
              │ │ │ -(21)             return;
              │ │ │ -(22)         } else {
              │ │ │ -(23)             ibuf[-1] = DIST_MAGIC_RECV_TAG; /* XXX */
              │ │ │ -(24)             driver_output(ud->port,ibuf - 1, res + 1);
              │ │ │ -(25)             driver_select(ud->port, (ErlDrvEvent) ud->fd, DO_READ,1);
              │ │ │ -(26)         }
              │ │ │ -(27)     }
              │ │ │ -(28) }

              The routine tries to read data until a packet is read or the │ │ │ +(19) driver_output(ud->port,ibuf - 1, res + 1); │ │ │ +(20) driver_select(ud->port, (ErlDrvEvent) ud->fd, DO_READ,0); │ │ │ +(21) return; │ │ │ +(22) } else { │ │ │ +(23) ibuf[-1] = DIST_MAGIC_RECV_TAG; /* XXX */ │ │ │ +(24) driver_output(ud->port,ibuf - 1, res + 1); │ │ │ +(25) driver_select(ud->port, (ErlDrvEvent) ud->fd, DO_READ,1); │ │ │ +(26) } │ │ │ +(27) } │ │ │ +(28) }

              The routine tries to read data until a packet is read or the │ │ │ buffered_read_package routine returns a NORMAL_READ_FAILURE (an internally │ │ │ defined constant for the module, which means that the read operation resulted in │ │ │ an EWOULDBLOCK). If the port is in command mode, the reading stops when one │ │ │ package is read. If the port is in data mode, the reading continues until the │ │ │ socket buffer is empty (read failure). If no more data can be read and more is │ │ │ wanted (which is always the case when the socket is in data mode), │ │ │ driver_select is called to make the uds_input callback be called when more │ │ │ data is available for reading.

              When the port is in data mode, all data is sent to Erlang in a format that │ │ │ suits the distribution. In fact, the raw data will never reach any Erlang │ │ │ process, but will be translated/interpreted by the emulator itself and then │ │ │ delivered in the correct format to the correct processes. In the current │ │ │ emulator version, received data is to be tagged with a single byte of 100. That │ │ │ is what the macro DIST_MAGIC_RECV_TAG is defined to. The tagging of data in │ │ │ the distribution can be changed in the future.

              The uds_input routine handles other input events (like non-blocking accept), │ │ │ -but most importantly handle data arriving at the socket by calling do_recv:

              ( 1) static void uds_input(ErlDrvData handle, ErlDrvEvent event)
              │ │ │ -( 2) {
              │ │ │ -( 3)     UdsData *ud = (UdsData *) handle;
              │ │ │ -
              │ │ │ -( 4)     if (ud->type == portTypeListener) {
              │ │ │ -( 5)         UdsData *ad = ud->partner;
              │ │ │ -( 6)         struct sockaddr_un peer;
              │ │ │ -( 7)         int pl = sizeof(struct sockaddr_un);
              │ │ │ -( 8)         int fd;
              │ │ │ -
              │ │ │ -( 9)         if ((fd = accept(ud->fd, (struct sockaddr *) &peer, &pl)) < 0) {
              │ │ │ -(10)             if (errno != EWOULDBLOCK) {
              │ │ │ -(11)                 driver_failure_posix(ud->port, errno);
              │ │ │ -(12)                 return;
              │ │ │ -(13)             }
              │ │ │ -(14)             return;
              │ │ │ -(15)         }
              │ │ │ -(16)         SET_NONBLOCKING(fd);
              │ │ │ -(17)         ad->fd = fd;
              │ │ │ -(18)         ad->partner = NULL;
              │ │ │ -(19)         ad->type = portTypeCommand;
              │ │ │ -(20)         ud->partner = NULL;
              │ │ │ -(21)         driver_select(ud->port, (ErlDrvEvent) ud->fd, DO_READ, 0);
              │ │ │ -(22)         driver_output(ad->port, "Aok",3);
              │ │ │ -(23)         return;
              │ │ │ -(24)     }
              │ │ │ -(25)     do_recv(ud);
              │ │ │ -(26) }

              The important line is the last line in the function: the do_read routine is │ │ │ +but most importantly handle data arriving at the socket by calling do_recv:

              ( 1) static void uds_input(ErlDrvData handle, ErlDrvEvent event)
              │ │ │ +( 2) {
              │ │ │ +( 3)     UdsData *ud = (UdsData *) handle;
              │ │ │ +
              │ │ │ +( 4)     if (ud->type == portTypeListener) {
              │ │ │ +( 5)         UdsData *ad = ud->partner;
              │ │ │ +( 6)         struct sockaddr_un peer;
              │ │ │ +( 7)         int pl = sizeof(struct sockaddr_un);
              │ │ │ +( 8)         int fd;
              │ │ │ +
              │ │ │ +( 9)         if ((fd = accept(ud->fd, (struct sockaddr *) &peer, &pl)) < 0) {
              │ │ │ +(10)             if (errno != EWOULDBLOCK) {
              │ │ │ +(11)                 driver_failure_posix(ud->port, errno);
              │ │ │ +(12)                 return;
              │ │ │ +(13)             }
              │ │ │ +(14)             return;
              │ │ │ +(15)         }
              │ │ │ +(16)         SET_NONBLOCKING(fd);
              │ │ │ +(17)         ad->fd = fd;
              │ │ │ +(18)         ad->partner = NULL;
              │ │ │ +(19)         ad->type = portTypeCommand;
              │ │ │ +(20)         ud->partner = NULL;
              │ │ │ +(21)         driver_select(ud->port, (ErlDrvEvent) ud->fd, DO_READ, 0);
              │ │ │ +(22)         driver_output(ad->port, "Aok",3);
              │ │ │ +(23)         return;
              │ │ │ +(24)     }
              │ │ │ +(25)     do_recv(ud);
              │ │ │ +(26) }

              The important line is the last line in the function: the do_read routine is │ │ │ called to handle new input. The remaining function handles input on a listen │ │ │ socket, which means that it is to be possible to do an accept on the socket, │ │ │ which is also recognized as a read event.

              The output mechanisms are similar to the input. The do_send routine is as │ │ │ -follows:

              ( 1) static void do_send(UdsData *ud, char *buff, int bufflen)
              │ │ │ -( 2) {
              │ │ │ -( 3)     char header[4];
              │ │ │ -( 4)     int written;
              │ │ │ -( 5)     SysIOVec iov[2];
              │ │ │ -( 6)     ErlIOVec eio;
              │ │ │ -( 7)     ErlDrvBinary *binv[] = {NULL,NULL};
              │ │ │ -
              │ │ │ -( 8)     put_packet_length(header, bufflen);
              │ │ │ -( 9)     iov[0].iov_base = (char *) header;
              │ │ │ -(10)     iov[0].iov_len = 4;
              │ │ │ -(11)     iov[1].iov_base = buff;
              │ │ │ -(12)     iov[1].iov_len = bufflen;
              │ │ │ -(13)     eio.iov = iov;
              │ │ │ -(14)     eio.binv = binv;
              │ │ │ -(15)     eio.vsize = 2;
              │ │ │ -(16)     eio.size = bufflen + 4;
              │ │ │ -(17)     written = 0;
              │ │ │ -(18)     if (driver_sizeq(ud->port) == 0) {
              │ │ │ -(19)         if ((written = writev(ud->fd, iov, 2)) == eio.size) {
              │ │ │ -(20)             ud->sent += written;
              │ │ │ -(21)             if (ud->type == portTypeCommand) {
              │ │ │ -(22)                 driver_output(ud->port, "Sok", 3);
              │ │ │ -(23)             }
              │ │ │ -(24)             return;
              │ │ │ -(25)         } else if (written < 0) {
              │ │ │ -(26)             if (errno != EWOULDBLOCK) {
              │ │ │ -(27)                 driver_failure_eof(ud->port);
              │ │ │ -(28)                 return;
              │ │ │ -(29)             } else {
              │ │ │ -(30)                 written = 0;
              │ │ │ -(31)             }
              │ │ │ -(32)         } else {
              │ │ │ -(33)             ud->sent += written;
              │ │ │ -(34)         }
              │ │ │ -(35)         /* Enqueue remaining */
              │ │ │ -(36)     }
              │ │ │ -(37)     driver_enqv(ud->port, &eio, written);
              │ │ │ -(38)     send_out_queue(ud);
              │ │ │ -(39) }

              This driver uses the writev system call to send data onto the socket. A │ │ │ +follows:

              ( 1) static void do_send(UdsData *ud, char *buff, int bufflen)
              │ │ │ +( 2) {
              │ │ │ +( 3)     char header[4];
              │ │ │ +( 4)     int written;
              │ │ │ +( 5)     SysIOVec iov[2];
              │ │ │ +( 6)     ErlIOVec eio;
              │ │ │ +( 7)     ErlDrvBinary *binv[] = {NULL,NULL};
              │ │ │ +
              │ │ │ +( 8)     put_packet_length(header, bufflen);
              │ │ │ +( 9)     iov[0].iov_base = (char *) header;
              │ │ │ +(10)     iov[0].iov_len = 4;
              │ │ │ +(11)     iov[1].iov_base = buff;
              │ │ │ +(12)     iov[1].iov_len = bufflen;
              │ │ │ +(13)     eio.iov = iov;
              │ │ │ +(14)     eio.binv = binv;
              │ │ │ +(15)     eio.vsize = 2;
              │ │ │ +(16)     eio.size = bufflen + 4;
              │ │ │ +(17)     written = 0;
              │ │ │ +(18)     if (driver_sizeq(ud->port) == 0) {
              │ │ │ +(19)         if ((written = writev(ud->fd, iov, 2)) == eio.size) {
              │ │ │ +(20)             ud->sent += written;
              │ │ │ +(21)             if (ud->type == portTypeCommand) {
              │ │ │ +(22)                 driver_output(ud->port, "Sok", 3);
              │ │ │ +(23)             }
              │ │ │ +(24)             return;
              │ │ │ +(25)         } else if (written < 0) {
              │ │ │ +(26)             if (errno != EWOULDBLOCK) {
              │ │ │ +(27)                 driver_failure_eof(ud->port);
              │ │ │ +(28)                 return;
              │ │ │ +(29)             } else {
              │ │ │ +(30)                 written = 0;
              │ │ │ +(31)             }
              │ │ │ +(32)         } else {
              │ │ │ +(33)             ud->sent += written;
              │ │ │ +(34)         }
              │ │ │ +(35)         /* Enqueue remaining */
              │ │ │ +(36)     }
              │ │ │ +(37)     driver_enqv(ud->port, &eio, written);
              │ │ │ +(38)     send_out_queue(ud);
              │ │ │ +(39) }

              This driver uses the writev system call to send data onto the socket. A │ │ │ combination of writev and the driver output queues is very convenient. An │ │ │ ErlIOVec structure contains a SysIOVec (which is equivalent to the │ │ │ struct iovec structure defined in uio.h. The ErlIOVec also contains an │ │ │ array of ErlDrvBinary pointers, of the same length as the number of buffers in │ │ │ the I/O vector itself. One can use this to allocate the binaries for the queue │ │ │ "manually" in the driver, but here the binary array is filled with NULL values │ │ │ (line 7). The runtime system then allocates its own buffers when driver_enqv │ │ │ @@ -965,60 +965,60 @@ │ │ │ opcode has been removed and the buffer length decreased by the output routine). │ │ │ If the queue is empty, we write the data directly to the socket (or at least try │ │ │ to). If any data is left, it is stored in the queue and then we try to send the │ │ │ queue (line 38). An acknowledgement is sent when the message is delivered │ │ │ completely (line 22). The send_out_queue sends acknowledgements if the sending │ │ │ is completed there. If the port is in command mode, the Erlang code serializes │ │ │ the send operations so that only one packet can be waiting for delivery at a │ │ │ -time. Therefore the acknowledgement can be sent whenever the queue is empty.

              The send_out_queue routine is as follows:

              ( 1) static int send_out_queue(UdsData *ud)
              │ │ │ -( 2) {
              │ │ │ -( 3)     for(;;) {
              │ │ │ -( 4)         int vlen;
              │ │ │ -( 5)         SysIOVec *tmp = driver_peekq(ud->port, &vlen);
              │ │ │ -( 6)         int wrote;
              │ │ │ -( 7)         if (tmp == NULL) {
              │ │ │ -( 8)             driver_select(ud->port, (ErlDrvEvent) ud->fd, DO_WRITE, 0);
              │ │ │ -( 9)             if (ud->type == portTypeCommand) {
              │ │ │ -(10)                 driver_output(ud->port, "Sok", 3);
              │ │ │ -(11)             }
              │ │ │ -(12)             return 0;
              │ │ │ -(13)         }
              │ │ │ -(14)         if (vlen > IO_VECTOR_MAX) {
              │ │ │ -(15)             vlen = IO_VECTOR_MAX;
              │ │ │ -(16)         }
              │ │ │ -(17)         if ((wrote = writev(ud->fd, tmp, vlen)) < 0) {
              │ │ │ -(18)             if (errno == EWOULDBLOCK) {
              │ │ │ -(19)                 driver_select(ud->port, (ErlDrvEvent) ud->fd,
              │ │ │ -(20)                               DO_WRITE, 1);
              │ │ │ -(21)                 return 0;
              │ │ │ -(22)             } else {
              │ │ │ -(23)                 driver_failure_eof(ud->port);
              │ │ │ -(24)                 return -1;
              │ │ │ -(25)             }
              │ │ │ -(26)         }
              │ │ │ -(27)         driver_deq(ud->port, wrote);
              │ │ │ -(28)         ud->sent += wrote;
              │ │ │ -(29)     }
              │ │ │ -(30) }

              We simply pick out an I/O vector from the queue (which is the whole queue as a │ │ │ +time. Therefore the acknowledgement can be sent whenever the queue is empty.

              The send_out_queue routine is as follows:

              ( 1) static int send_out_queue(UdsData *ud)
              │ │ │ +( 2) {
              │ │ │ +( 3)     for(;;) {
              │ │ │ +( 4)         int vlen;
              │ │ │ +( 5)         SysIOVec *tmp = driver_peekq(ud->port, &vlen);
              │ │ │ +( 6)         int wrote;
              │ │ │ +( 7)         if (tmp == NULL) {
              │ │ │ +( 8)             driver_select(ud->port, (ErlDrvEvent) ud->fd, DO_WRITE, 0);
              │ │ │ +( 9)             if (ud->type == portTypeCommand) {
              │ │ │ +(10)                 driver_output(ud->port, "Sok", 3);
              │ │ │ +(11)             }
              │ │ │ +(12)             return 0;
              │ │ │ +(13)         }
              │ │ │ +(14)         if (vlen > IO_VECTOR_MAX) {
              │ │ │ +(15)             vlen = IO_VECTOR_MAX;
              │ │ │ +(16)         }
              │ │ │ +(17)         if ((wrote = writev(ud->fd, tmp, vlen)) < 0) {
              │ │ │ +(18)             if (errno == EWOULDBLOCK) {
              │ │ │ +(19)                 driver_select(ud->port, (ErlDrvEvent) ud->fd,
              │ │ │ +(20)                               DO_WRITE, 1);
              │ │ │ +(21)                 return 0;
              │ │ │ +(22)             } else {
              │ │ │ +(23)                 driver_failure_eof(ud->port);
              │ │ │ +(24)                 return -1;
              │ │ │ +(25)             }
              │ │ │ +(26)         }
              │ │ │ +(27)         driver_deq(ud->port, wrote);
              │ │ │ +(28)         ud->sent += wrote;
              │ │ │ +(29)     }
              │ │ │ +(30) }

              We simply pick out an I/O vector from the queue (which is the whole queue as a │ │ │ SysIOVec). If the I/O vector is too long (IO_VECTOR_MAX is defined to 16), │ │ │ the vector length is decreased (line 15), otherwise the writev call (line 17) │ │ │ fails. Writing is tried and anything written is dequeued (line 27). If the write │ │ │ fails with EWOULDBLOCK (notice that all sockets are in non-blocking mode), │ │ │ driver_select is called to make the uds_output routine be called when there │ │ │ -is space to write again.

              We continue trying to write until the queue is empty or the writing blocks.

              The routine above is called from the uds_output routine:

              ( 1) static void uds_output(ErlDrvData handle, ErlDrvEvent event)
              │ │ │ -( 2) {
              │ │ │ -( 3)    UdsData *ud = (UdsData *) handle;
              │ │ │ -( 4)    if (ud->type == portTypeConnector) {
              │ │ │ -( 5)        ud->type = portTypeCommand;
              │ │ │ -( 6)        driver_select(ud->port, (ErlDrvEvent) ud->fd, DO_WRITE, 0);
              │ │ │ -( 7)        driver_output(ud->port, "Cok",3);
              │ │ │ -( 8)        return;
              │ │ │ -( 9)    }
              │ │ │ -(10)    send_out_queue(ud);
              │ │ │ -(11) }

              The routine is simple: it first handles the fact that the output select will │ │ │ +is space to write again.

              We continue trying to write until the queue is empty or the writing blocks.

              The routine above is called from the uds_output routine:

              ( 1) static void uds_output(ErlDrvData handle, ErlDrvEvent event)
              │ │ │ +( 2) {
              │ │ │ +( 3)    UdsData *ud = (UdsData *) handle;
              │ │ │ +( 4)    if (ud->type == portTypeConnector) {
              │ │ │ +( 5)        ud->type = portTypeCommand;
              │ │ │ +( 6)        driver_select(ud->port, (ErlDrvEvent) ud->fd, DO_WRITE, 0);
              │ │ │ +( 7)        driver_output(ud->port, "Cok",3);
              │ │ │ +( 8)        return;
              │ │ │ +( 9)    }
              │ │ │ +(10)    send_out_queue(ud);
              │ │ │ +(11) }

              The routine is simple: it first handles the fact that the output select will │ │ │ concern a socket in the business of connecting (and the connecting blocked). If │ │ │ the socket is in a connected state, it simply sends the output queue. This │ │ │ routine is called when it is possible to write to a socket where we have an │ │ │ output queue, so there is no question what to do.

              The driver implements a control interface, which is a synchronous interface │ │ │ called when Erlang calls erlang:port_control/3. Only this interface can │ │ │ control the driver when it is in data mode. It can be called with the │ │ │ following opcodes:

              • 'C' - Sets port in command mode.

              • 'I' - Sets port in intermediate mode.

              • 'D' - Sets port in data mode.

              • 'N' - Gets identification number for listen port. This identification │ │ │ @@ -1034,93 +1034,93 @@ │ │ │ caller. If erlang:port_command is used, use erlang:port_command/3 and pass │ │ │ [force] as option list; otherwise the caller can be blocked indefinitely on │ │ │ a busy port and prevent the system from taking down a connection that is not │ │ │ functioning.

              • 'R' - Gets creation number of a listen socket, which is used to dig out │ │ │ the number stored in the lock file to differentiate between invocations of │ │ │ Erlang nodes with the same name.

              The control interface gets a buffer to return its value in, but is free to │ │ │ allocate its own buffer if the provided one is too small. The uds_control code │ │ │ -is as follows:

              ( 1) static int uds_control(ErlDrvData handle, unsigned int command,
              │ │ │ -( 2)                        char* buf, int count, char** res, int res_size)
              │ │ │ -( 3) {
              │ │ │ -( 4) /* Local macro to ensure large enough buffer. */
              │ │ │ -( 5) #define ENSURE(N)                               \
              │ │ │ -( 6)    do {                                         \
              │ │ │ -( 7)        if (res_size < N) {                      \
              │ │ │ -( 8)            *res = ALLOC(N);                     \
              │ │ │ -( 9)        }                                        \
              │ │ │ -(10)    } while(0)
              │ │ │ -
              │ │ │ -(11)    UdsData *ud = (UdsData *) handle;
              │ │ │ -
              │ │ │ -(12)    switch (command) {
              │ │ │ -(13)    case 'S':
              │ │ │ -(14)        {
              │ │ │ -(15)            ENSURE(13);
              │ │ │ -(16)            **res = 0;
              │ │ │ -(17)            put_packet_length((*res) + 1, ud->received);
              │ │ │ -(18)            put_packet_length((*res) + 5, ud->sent);
              │ │ │ -(19)            put_packet_length((*res) + 9, driver_sizeq(ud->port));
              │ │ │ -(20)            return 13;
              │ │ │ -(21)        }
              │ │ │ -(22)    case 'C':
              │ │ │ -(23)        if (ud->type < portTypeCommand) {
              │ │ │ -(24)            return report_control_error(res, res_size, "einval");
              │ │ │ -(25)        }
              │ │ │ -(26)        ud->type = portTypeCommand;
              │ │ │ -(27)        driver_select(ud->port, (ErlDrvEvent) ud->fd, DO_READ, 0);
              │ │ │ -(28)        ENSURE(1);
              │ │ │ -(29)        **res = 0;
              │ │ │ -(30)        return 1;
              │ │ │ -(31)    case 'I':
              │ │ │ -(32)        if (ud->type < portTypeCommand) {
              │ │ │ -(33)            return report_control_error(res, res_size, "einval");
              │ │ │ -(34)        }
              │ │ │ -(35)        ud->type = portTypeIntermediate;
              │ │ │ -(36)        driver_select(ud->port, (ErlDrvEvent) ud->fd, DO_READ, 0);
              │ │ │ -(37)        ENSURE(1);
              │ │ │ -(38)        **res = 0;
              │ │ │ -(39)        return 1;
              │ │ │ -(40)    case 'D':
              │ │ │ -(41)        if (ud->type < portTypeCommand) {
              │ │ │ -(42)            return report_control_error(res, res_size, "einval");
              │ │ │ -(43)        }
              │ │ │ -(44)        ud->type = portTypeData;
              │ │ │ -(45)        do_recv(ud);
              │ │ │ -(46)        ENSURE(1);
              │ │ │ -(47)        **res = 0;
              │ │ │ -(48)        return 1;
              │ │ │ -(49)    case 'N':
              │ │ │ -(50)        if (ud->type != portTypeListener) {
              │ │ │ -(51)            return report_control_error(res, res_size, "einval");
              │ │ │ -(52)        }
              │ │ │ -(53)        ENSURE(5);
              │ │ │ -(54)        (*res)[0] = 0;
              │ │ │ -(55)        put_packet_length((*res) + 1, ud->fd);
              │ │ │ -(56)        return 5;
              │ │ │ -(57)    case 'T': /* tick */
              │ │ │ -(58)        if (ud->type != portTypeData) {
              │ │ │ -(59)            return report_control_error(res, res_size, "einval");
              │ │ │ -(60)        }
              │ │ │ -(61)        do_send(ud,"",0);
              │ │ │ -(62)        ENSURE(1);
              │ │ │ -(63)        **res = 0;
              │ │ │ -(64)        return 1;
              │ │ │ -(65)    case 'R':
              │ │ │ -(66)        if (ud->type != portTypeListener) {
              │ │ │ -(67)            return report_control_error(res, res_size, "einval");
              │ │ │ -(68)        }
              │ │ │ -(69)        ENSURE(2);
              │ │ │ -(70)        (*res)[0] = 0;
              │ │ │ -(71)        (*res)[1] = ud->creation;
              │ │ │ -(72)        return 2;
              │ │ │ -(73)    default:
              │ │ │ -(74)        return report_control_error(res, res_size, "einval");
              │ │ │ -(75)    }
              │ │ │ -(76) #undef ENSURE
              │ │ │ -(77) }

              The macro ENSURE (line 5-10) is used to ensure that the buffer is large enough │ │ │ +is as follows:

              ( 1) static int uds_control(ErlDrvData handle, unsigned int command,
              │ │ │ +( 2)                        char* buf, int count, char** res, int res_size)
              │ │ │ +( 3) {
              │ │ │ +( 4) /* Local macro to ensure large enough buffer. */
              │ │ │ +( 5) #define ENSURE(N)                               \
              │ │ │ +( 6)    do {                                         \
              │ │ │ +( 7)        if (res_size < N) {                      \
              │ │ │ +( 8)            *res = ALLOC(N);                     \
              │ │ │ +( 9)        }                                        \
              │ │ │ +(10)    } while(0)
              │ │ │ +
              │ │ │ +(11)    UdsData *ud = (UdsData *) handle;
              │ │ │ +
              │ │ │ +(12)    switch (command) {
              │ │ │ +(13)    case 'S':
              │ │ │ +(14)        {
              │ │ │ +(15)            ENSURE(13);
              │ │ │ +(16)            **res = 0;
              │ │ │ +(17)            put_packet_length((*res) + 1, ud->received);
              │ │ │ +(18)            put_packet_length((*res) + 5, ud->sent);
              │ │ │ +(19)            put_packet_length((*res) + 9, driver_sizeq(ud->port));
              │ │ │ +(20)            return 13;
              │ │ │ +(21)        }
              │ │ │ +(22)    case 'C':
              │ │ │ +(23)        if (ud->type < portTypeCommand) {
              │ │ │ +(24)            return report_control_error(res, res_size, "einval");
              │ │ │ +(25)        }
              │ │ │ +(26)        ud->type = portTypeCommand;
              │ │ │ +(27)        driver_select(ud->port, (ErlDrvEvent) ud->fd, DO_READ, 0);
              │ │ │ +(28)        ENSURE(1);
              │ │ │ +(29)        **res = 0;
              │ │ │ +(30)        return 1;
              │ │ │ +(31)    case 'I':
              │ │ │ +(32)        if (ud->type < portTypeCommand) {
              │ │ │ +(33)            return report_control_error(res, res_size, "einval");
              │ │ │ +(34)        }
              │ │ │ +(35)        ud->type = portTypeIntermediate;
              │ │ │ +(36)        driver_select(ud->port, (ErlDrvEvent) ud->fd, DO_READ, 0);
              │ │ │ +(37)        ENSURE(1);
              │ │ │ +(38)        **res = 0;
              │ │ │ +(39)        return 1;
              │ │ │ +(40)    case 'D':
              │ │ │ +(41)        if (ud->type < portTypeCommand) {
              │ │ │ +(42)            return report_control_error(res, res_size, "einval");
              │ │ │ +(43)        }
              │ │ │ +(44)        ud->type = portTypeData;
              │ │ │ +(45)        do_recv(ud);
              │ │ │ +(46)        ENSURE(1);
              │ │ │ +(47)        **res = 0;
              │ │ │ +(48)        return 1;
              │ │ │ +(49)    case 'N':
              │ │ │ +(50)        if (ud->type != portTypeListener) {
              │ │ │ +(51)            return report_control_error(res, res_size, "einval");
              │ │ │ +(52)        }
              │ │ │ +(53)        ENSURE(5);
              │ │ │ +(54)        (*res)[0] = 0;
              │ │ │ +(55)        put_packet_length((*res) + 1, ud->fd);
              │ │ │ +(56)        return 5;
              │ │ │ +(57)    case 'T': /* tick */
              │ │ │ +(58)        if (ud->type != portTypeData) {
              │ │ │ +(59)            return report_control_error(res, res_size, "einval");
              │ │ │ +(60)        }
              │ │ │ +(61)        do_send(ud,"",0);
              │ │ │ +(62)        ENSURE(1);
              │ │ │ +(63)        **res = 0;
              │ │ │ +(64)        return 1;
              │ │ │ +(65)    case 'R':
              │ │ │ +(66)        if (ud->type != portTypeListener) {
              │ │ │ +(67)            return report_control_error(res, res_size, "einval");
              │ │ │ +(68)        }
              │ │ │ +(69)        ENSURE(2);
              │ │ │ +(70)        (*res)[0] = 0;
              │ │ │ +(71)        (*res)[1] = ud->creation;
              │ │ │ +(72)        return 2;
              │ │ │ +(73)    default:
              │ │ │ +(74)        return report_control_error(res, res_size, "einval");
              │ │ │ +(75)    }
              │ │ │ +(76) #undef ENSURE
              │ │ │ +(77) }

              The macro ENSURE (line 5-10) is used to ensure that the buffer is large enough │ │ │ for the answer. We switch on the command and take actions. We always have read │ │ │ select active on a port in data mode (achieved by calling do_recv on line │ │ │ 45), but we turn off read selection in intermediate and command modes (line │ │ │ 27 and 36).

              The rest of the driver is more or less UDS-specific and not of general interest.

              │ │ │ │ │ │ │ │ │ │ │ ├── ./usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/automaticyieldingofccode.html │ │ │ @@ -209,17 +209,17 @@ │ │ │ they have to follow certain restrictions. The convention for making │ │ │ this clear is to have a comment above the function that explains that │ │ │ the function is transformed by YCF (see maps_values_1_helper in │ │ │ erl_map.c for an example). If only the transformed version of the │ │ │ function is used, the convention is to "comment out" the source for the │ │ │ function by surrounding it with the following #ifdef (this way, one │ │ │ will not get warnings about unused functions):

              #ifdef INCLUDE_YCF_TRANSFORMED_ONLY_FUNCTIONS
              │ │ │ -void my_fun() {
              │ │ │ +void my_fun() {
              │ │ │      ...
              │ │ │ -}
              │ │ │ +}
              │ │ │  #endif /* INCLUDE_YCF_TRANSFORMED_ONLY_FUNCTIONS */

              While editing the function one can define │ │ │ INCLUDE_YCF_TRANSFORMED_ONLY_FUNCTIONS so that one can see errors │ │ │ and warnings in the non-transformed source.

              │ │ │ │ │ │ │ │ │ │ │ │ Where to Place YCF Transformed Functions │ │ ├── ./usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/beam_makeops.html │ │ │ @@ -151,17 +151,17 @@ │ │ │ The loader translates generic instructions to specific instructions. │ │ │ In general, for each generic instruction, there exists a family of │ │ │ specific instructions. The OTP 20 release has 389 specific │ │ │ instructions.

            • The implementation of specific instructions for the traditional │ │ │ BEAM interpreter. For the BeamAsm JIT introduced │ │ │ in OTP 24, the implementation of instructions are defined in emitter │ │ │ functions written in C++.

            Generic instructions have typed operands. Here are a few examples of │ │ │ -operands for move/2:

            {move,{atom,id},{x,5}}.
            │ │ │ -{move,{x,3},{x,0}}.
            │ │ │ -{move,{x,2},{y,1}}.

            When those instructions are loaded, the loader rewrites them │ │ │ +operands for move/2:

            {move,{atom,id},{x,5}}.
            │ │ │ +{move,{x,3},{x,0}}.
            │ │ │ +{move,{x,2},{y,1}}.

            When those instructions are loaded, the loader rewrites them │ │ │ to specific instructions:

            move_cx id 5
            │ │ │  move_xx 3 0
            │ │ │  move_xy 2 1

            Corresponding to each generic instruction, there is a family of │ │ │ specific instructions. The types that an instance of a specific │ │ │ instruction can handle are encoded in the instruction names. For │ │ │ example, move_xy takes an X register number as the first operand and │ │ │ a Y register number as the second operand. move_cx takes a tagged │ │ │ @@ -185,17 +185,17 @@ │ │ │ move c x

    Each specific instructions is defined by following the name of the │ │ │ instruction with the types for each operand. An operand type is a │ │ │ single letter. For example, x means an X register, y │ │ │ means a Y register, and c is a "constant" (a tagged term such as │ │ │ an integer, an atom, or a literal).

    Now let's look at the implementation of the move instruction. There │ │ │ are multiple files containing implementations of instructions in the │ │ │ erts/emulator/beam/emu directory. The move instruction is defined │ │ │ -in instrs.tab. It looks like this:

    move(Src, Dst) {
    │ │ │ +in instrs.tab.  It looks like this:

    move(Src, Dst) {
    │ │ │      $Dst = $Src;
    │ │ │ -}

    The implementation for an instruction largely follows the C syntax, │ │ │ +}

    The implementation for an instruction largely follows the C syntax, │ │ │ except that the variables in the function head don't have any types. │ │ │ The $ before an identifier denotes a macro expansion. Thus, │ │ │ $Src will expand to the code to pick up the source operand for │ │ │ the instruction and $Dst to the code for the destination register.

    We will look at the code for each specific instruction in turn. To │ │ │ make the code easier to understand, let's first look at the memory │ │ │ layout for the instruction {move,{atom,id},{x,5}}:

         +--------------------+--------------------+
    │ │ │  I -> |                 40 |       &&lb_move_cx |
    │ │ │ @@ -204,61 +204,61 @@
    │ │ │       +--------------------+--------------------+

    This example and all other examples in the document assumes a 64-bit │ │ │ architecture, and furthermore that pointers to C code fit in 32 bits.

    I in the BEAM virtual machine is the instruction pointer. When BEAM │ │ │ executes an instruction, I points to the first word of the │ │ │ instruction.

    &&lb_move_cx is the address to C code that implements move_cx. It │ │ │ is stored in the lower 32 bits of the word. In the upper 32 bits is │ │ │ the byte offset to the X register; the register number 5 has been │ │ │ multiplied by the word size size 8.

    In the next word the tagged atom id is stored.

    With that background, we can look at the generated code for move_cx │ │ │ -in beam_hot.h:

    OpCase(move_cx):
    │ │ │ -{
    │ │ │ -  BeamInstr next_pf = BeamCodeAddr(I[2]);
    │ │ │ -  xb(BeamExtraData(I[0])) = I[1];
    │ │ │ +in beam_hot.h:

    OpCase(move_cx):
    │ │ │ +{
    │ │ │ +  BeamInstr next_pf = BeamCodeAddr(I[2]);
    │ │ │ +  xb(BeamExtraData(I[0])) = I[1];
    │ │ │    I += 2;
    │ │ │ -  ASSERT(VALID_INSTR(next_pf));
    │ │ │ -  GotoPF(next_pf);
    │ │ │ -}

    We will go through each line in turn.

    • OpCase(move_cx): defines a label for the instruction. The │ │ │ + ASSERT(VALID_INSTR(next_pf)); │ │ │ + GotoPF(next_pf); │ │ │ +}

    We will go through each line in turn.

    • OpCase(move_cx): defines a label for the instruction. The │ │ │ OpCase() macro is defined in beam_emu.c. It will expand this line │ │ │ to lb_move_cx:.

    • BeamInstr next_pf = BeamCodeAddr(I[2]); fetches the pointer to │ │ │ code for the next instruction to be executed. The BeamCodeAddr() │ │ │ macro extracts the pointer from the lower 32 bits of the instruction │ │ │ word.

    • xb(BeamExtraData(I[0])) = I[1]; is the expansion of $Dst = $Src. │ │ │ BeamExtraData() is a macro that will extract the upper 32 bits from │ │ │ the instruction word. In this example, it will return 40 which is the │ │ │ byte offset for X register 5. The xb() macro will cast a byte │ │ │ pointer to an Eterm pointer and dereference it. The I[1] on │ │ │ the right-hand side of the = fetches an Erlang term (the atom id in │ │ │ this case).

    • I += 2 advances the instruction pointer to the next │ │ │ instruction.

    • In a debug-compiled emulator, ASSERT(VALID_INSTR(next_pf)); makes │ │ │ sure that next_pf is a valid instruction (that is, that it points │ │ │ -within the process_main() function in beam_emu.c).

    • GotoPF(next_pf); transfers control to the next instruction.

    Now let's look at the implementation of move_xx:

    OpCase(move_xx):
    │ │ │ -{
    │ │ │ -  Eterm tmp_packed1 = BeamExtraData(I[0]);
    │ │ │ -  BeamInstr next_pf = BeamCodeAddr(I[1]);
    │ │ │ -  xb((tmp_packed1>>BEAM_TIGHT_SHIFT)) = xb(tmp_packed1&BEAM_TIGHT_MASK);
    │ │ │ +within the process_main() function in beam_emu.c).

  • GotoPF(next_pf); transfers control to the next instruction.

  • Now let's look at the implementation of move_xx:

    OpCase(move_xx):
    │ │ │ +{
    │ │ │ +  Eterm tmp_packed1 = BeamExtraData(I[0]);
    │ │ │ +  BeamInstr next_pf = BeamCodeAddr(I[1]);
    │ │ │ +  xb((tmp_packed1>>BEAM_TIGHT_SHIFT)) = xb(tmp_packed1&BEAM_TIGHT_MASK);
    │ │ │    I += 1;
    │ │ │ -  ASSERT(VALID_INSTR(next_pf));
    │ │ │ -  GotoPF(next_pf);
    │ │ │ -}

    We will go through the lines that are new or have changed compared to │ │ │ + ASSERT(VALID_INSTR(next_pf)); │ │ │ + GotoPF(next_pf); │ │ │ +}

    We will go through the lines that are new or have changed compared to │ │ │ move_cx.

    • Eterm tmp_packed1 = BeamExtraData(I[0]); picks up both X register │ │ │ numbers packed into the upper 32 bits of the instruction word.

    • BeamInstr next_pf = BeamCodeAddr(I[1]); pre-fetches the address of │ │ │ the next instruction. Note that because both X registers operands fits │ │ │ into the instruction word, the next instruction is in the very next │ │ │ word.

    • xb((tmp_packed1>>BEAM_TIGHT_SHIFT)) = xb(tmp_packed1&BEAM_TIGHT_MASK); │ │ │ copies the source to the destination. (For a 64-bit architecture, │ │ │ BEAM_TIGHT_SHIFT is 16 and BEAM_TIGHT_MASK is 0xFFFF.)

    • I += 1; advances the instruction pointer to the next instruction.

    move_xy is almost identical to move_xx. The only difference is │ │ │ the use of the yb() macro instead of xb() to reference the │ │ │ -destination register:

    OpCase(move_xy):
    │ │ │ -{
    │ │ │ -  Eterm tmp_packed1 = BeamExtraData(I[0]);
    │ │ │ -  BeamInstr next_pf = BeamCodeAddr(I[1]);
    │ │ │ -  yb((tmp_packed1>>BEAM_TIGHT_SHIFT)) = xb(tmp_packed1&BEAM_TIGHT_MASK);
    │ │ │ +destination register:

    OpCase(move_xy):
    │ │ │ +{
    │ │ │ +  Eterm tmp_packed1 = BeamExtraData(I[0]);
    │ │ │ +  BeamInstr next_pf = BeamCodeAddr(I[1]);
    │ │ │ +  yb((tmp_packed1>>BEAM_TIGHT_SHIFT)) = xb(tmp_packed1&BEAM_TIGHT_MASK);
    │ │ │    I += 1;
    │ │ │ -  ASSERT(VALID_INSTR(next_pf));
    │ │ │ -  GotoPF(next_pf);
    │ │ │ -}

    │ │ │ + ASSERT(VALID_INSTR(next_pf)); │ │ │ + GotoPF(next_pf); │ │ │ +}

    │ │ │ │ │ │ │ │ │ │ │ │ Transformation rules │ │ │

    │ │ │

    Next let's look at how we can do some optimizations using transformation │ │ │ rules. For simple instructions such as move/2, the instruction dispatch │ │ │ @@ -271,21 +271,21 @@ │ │ │ with an uppercase letter just as in Erlang. A pattern variable may be │ │ │ followed = and one or more type letters to constrain the match to │ │ │ one of those types. The variables that are bound on the left-hand side can │ │ │ be used on the right-hand side.

    We will also need to define a specific instruction and an implementation:

    # In ops.tab
    │ │ │  move2 x y x y
    │ │ │  
    │ │ │  // In instrs.tab
    │ │ │ -move2(S1, D1, S2, D2) {
    │ │ │ +move2(S1, D1, S2, D2) {
    │ │ │      Eterm V1, V2;
    │ │ │      V1 = $S1;
    │ │ │      V2 = $S2;
    │ │ │      $D1 = V1;
    │ │ │      $D2 = V2;
    │ │ │ -}

    When the loader has found a match and replaced the matched instructions, │ │ │ +}

    When the loader has found a match and replaced the matched instructions, │ │ │ it will match the new instructions against the transformation rules. │ │ │ Because of that, we can define the rule for a move3/6 instruction │ │ │ as follows:

    move2 X1=x Y1=y X2=x Y2=y | move X3=x Y3=y =>
    │ │ │        move3 X1 Y1 X2 Y2 X3 Y3

    (For readability, a long transformation line can be broken after | │ │ │ and => operators.)

    It would also be possible to define it like this:

    move X1=x Y1=y | move X2=x Y2=y | move X3=x Y3=y =>
    │ │ │       move3 X1 Y1 X2 Y2 X3 Y3

    but in that case it must be defined before the rule for move2/4 │ │ │ because the first matching rule will be applied.

    One must be careful not to create infinite loops. For example, if we │ │ │ @@ -433,29 +433,29 @@ │ │ │ i_bs_get_integer_32 x f? x │ │ │ %endif

    The specific instruction i_bs_get_integer_32 will only be defined │ │ │ on a 64-bit machine.

    The condition can be inverted by using %unless instead of %if:

    %unless NO_FPE_SIGNALS
    │ │ │  fcheckerror p => i_fcheckerror
    │ │ │  i_fcheckerror
    │ │ │  fclearerror
    │ │ │  %endif

    It is also possible to add an %else clause:

    %if ARCH_64
    │ │ │ -BS_SAFE_MUL(A, B, Fail, Dst) {
    │ │ │ -    Uint64 res = ($A) * ($B);
    │ │ │ -    if (res / $B != $A) {
    │ │ │ +BS_SAFE_MUL(A, B, Fail, Dst) {
    │ │ │ +    Uint64 res = ($A) * ($B);
    │ │ │ +    if (res / $B != $A) {
    │ │ │          $Fail;
    │ │ │ -    }
    │ │ │ +    }
    │ │ │      $Dst = res;
    │ │ │ -}
    │ │ │ +}
    │ │ │  %else
    │ │ │ -BS_SAFE_MUL(A, B, Fail, Dst) {
    │ │ │ -    Uint64 res = (Uint64)($A) * (Uint64)($B);
    │ │ │ -    if ((res >> (8*sizeof(Uint))) != 0) {
    │ │ │ +BS_SAFE_MUL(A, B, Fail, Dst) {
    │ │ │ +    Uint64 res = (Uint64)($A) * (Uint64)($B);
    │ │ │ +    if ((res >> (8*sizeof(Uint))) != 0) {
    │ │ │          $Fail;
    │ │ │ -    }
    │ │ │ +    }
    │ │ │      $Dst = res;
    │ │ │ -}
    │ │ │ +}
    │ │ │  %endif

    Symbols that are defined in directives

    The following symbols are always defined.

    • ARCH_64 - is 1 for a 64-bit machine, and 0 otherwise.
    • ARCH_32 - is 1 for 32-bit machine, and 0 otherwise.

    The Makefile for building the emulator currently defines the │ │ │ following symbols by using the -D option on the command line for │ │ │ beam_makeops.

    • USE_VM_PROBES - 1 if the runtime system is compiled to use VM │ │ │ probes (support for dtrace or systemtap), 0 otherwise.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -676,15 +676,15 @@ │ │ │ match both source and destination registers. As an operand in a specific │ │ │ instruction, it must only be used for a destination register.)

  • o - Overflow. An untagged integer that does not fit in a machine word.

  • Predicates

    If the constraints described so far is not enough, additional │ │ │ constraints can be implemented in C and be called as a guard function │ │ │ on the left-hand side of the transformation. If the guard function returns │ │ │ a non-zero value, the matching of the rule will continue, otherwise │ │ │ the match will fail. Such guard functions are hereafter called │ │ │ predicates.

    The most commonly used guard constraints is equal(). It can be used │ │ │ -to remove a redundant move instructio like this:

    move R1 R2 | equal(R1, R2) => _

    or remove a redundant is_eq_exact instruction like this:

    is_eq_exact Lbl Src1 Src2 | equal(Src1, Src2) => _

    At the time of writing, all predicates are defined in files named │ │ │ +to remove a redundant move instructio like this:

    move R1 R2 | equal(R1, R2) => _

    or remove a redundant is_eq_exact instruction like this:

    is_eq_exact Lbl Src1 Src2 | equal(Src1, Src2) => _

    At the time of writing, all predicates are defined in files named │ │ │ predicates.tab in several directories. In predicates.tab directly │ │ │ in $ERL_TOP/erts/emulator/beam, predicates that are used by both the │ │ │ traditinal emulator and the JIT implementations are contained. │ │ │ Predicates only used by the emulator can be found in │ │ │ emu/predicates.tab.

    │ │ │ │ │ │ │ │ │ @@ -692,41 +692,41 @@ │ │ │ A very brief note on implementation of predicates │ │ │

    │ │ │

    It is outside the scope for this document to describe in detail how │ │ │ predicates are implemented because it requires knowledge of the │ │ │ internal loader data structures, but here is quick look at the │ │ │ implementation of a simple predicate called literal_is_map().

    Here is first an example how it is used:

    ismap Fail Lit=q | literal_is_map(Lit) =>

    If the Lit operand is a literal, then the literal_is_map() │ │ │ predicate is called to determine whether it is a map literal. │ │ │ -If it is, the instruction is not needed and can be removed.

    literal_is_map() is implemented like this (in emu/predicates.tab):

    pred.literal_is_map(Lit) {
    │ │ │ +If it is, the instruction is not needed and can be removed.

    literal_is_map() is implemented like this (in emu/predicates.tab):

    pred.literal_is_map(Lit) {
    │ │ │      Eterm term;
    │ │ │  
    │ │ │ -    ASSERT(Lit.type == TAG_q);
    │ │ │ -    term = beamfile_get_literal(&S->beam, Lit.val);
    │ │ │ -    return is_map(term);
    │ │ │ -}

    The pred. prefix tells beam_makeops that this function is a │ │ │ + ASSERT(Lit.type == TAG_q); │ │ │ + term = beamfile_get_literal(&S->beam, Lit.val); │ │ │ + return is_map(term); │ │ │ +}

    The pred. prefix tells beam_makeops that this function is a │ │ │ predicate. Without the prefix, it would have been interpreted as the │ │ │ implementation of an instruction (described in Defining the │ │ │ implementation).

    Predicate functions have a magic variabled called S, which is a │ │ │ pointer to a state struct. In the example, │ │ │ beamfile_get_literal(&S->beam, Lit.val); is used to retrieve the actual term │ │ │ for the literal.

    At the time of writing, the expanded C code generated by │ │ │ -beam_makeops looks like this:

    static int literal_is_map(LoaderState* S, BeamOpArg Lit) {
    │ │ │ +beam_makeops looks like this:

    static int literal_is_map(LoaderState* S, BeamOpArg Lit) {
    │ │ │    Eterm term;
    │ │ │  
    │ │ │ -  ASSERT(Lit.type == TAG_q);
    │ │ │ -  term = S->literals[Lit.val].term;
    │ │ │ -  return is_map(term);;
    │ │ │ -}

    Handling instructions with variable number of operands

    Some instructions, such as select_val/3, essentially has a variable │ │ │ + ASSERT(Lit.type == TAG_q); │ │ │ + term = S->literals[Lit.val].term; │ │ │ + return is_map(term);; │ │ │ +}

    Handling instructions with variable number of operands

    Some instructions, such as select_val/3, essentially has a variable │ │ │ number of operands. Such instructions have a {list,[...]} operand │ │ │ -as their last operand in the BEAM assembly code. For example:

    {select_val,{x,0},
    │ │ │ -            {f,1},
    │ │ │ -            {list,[{atom,b},{f,4},{atom,a},{f,5}]}}.

    The loader will convert a {list,[...]} operand to an u operand whose │ │ │ +as their last operand in the BEAM assembly code. For example:

    {select_val,{x,0},
    │ │ │ +            {f,1},
    │ │ │ +            {list,[{atom,b},{f,4},{atom,a},{f,5}]}}.

    The loader will convert a {list,[...]} operand to an u operand whose │ │ │ value is the number of elements in the list, followed by each element in │ │ │ the list. The instruction above would be translated to the following │ │ │ -generic instruction:

    {select_val,{x,0},{f,1},{u,4},{atom,b},{f,4},{atom,a},{f,5}}

    To match a variable number of arguments we need to use the special │ │ │ +generic instruction:

    {select_val,{x,0},{f,1},{u,4},{atom,b},{f,4},{atom,a},{f,5}}

    To match a variable number of arguments we need to use the special │ │ │ operand type * like this:

    select_val Src=aiq Fail=f Size=u List=* =>
    │ │ │      i_const_select_val Src Fail Size List

    This transformation renames a select_val/3 instruction │ │ │ with a constant source operand to i_const_select_val/3.

    Constructing new instructions on the right-hand side

    The most common operand on the right-hand side is a variable that was │ │ │ bound while matching the pattern on the left-hand side. For example:

    trim N Remaining => i_trim N

    An operand can also be a type letter to construct an operand of that │ │ │ type. Each type has a default value. For example, the type x has │ │ │ the default value 1023, which is the highest X register. That makes │ │ │ x on the right-hand side a convenient shortcut for a temporary X │ │ │ @@ -746,53 +746,53 @@ │ │ │ transformation rule.

    • u - Construct an untagged integer. The default value is 0.

    • x - X register. The default value is 1023. That makes x convenient to │ │ │ use as a temporary X register.

    • y - Y register. The default value is 0.

    • l - Floating point register number. The default value is 0.

    • i - Tagged literal integer. The default value is 0.

    • a - Tagged atom. The default value is the empty atom (am_Empty).

    • p - Zero failure label.

    • n - NIL ([], the empty list).

    Function call on the right-hand side

    Transformations that are not possible to describe with the rule │ │ │ language as described here can be implemented as a generator function │ │ │ in C and called from the right-hand side of a transformation. The left-hand │ │ │ side of the transformation will perform the match and bind operands to │ │ │ variables. The variables can then be passed to a generator function │ │ │ on the right-hand side. For example:

    bif2 Fail=j u$bif:erlang:element/2 Index=s Tuple=xy Dst=d =>
    │ │ │ -    element(Jump, Index, Tuple, Dst)

    This transformation rule matches a call to the BIF element/2. │ │ │ + element(Jump, Index, Tuple, Dst)

    This transformation rule matches a call to the BIF element/2. │ │ │ The operands will be captured and the generator function element() will │ │ │ be called.

    The element() generator will produce one of two instructions │ │ │ depending on Index. If Index is an integer in the range from 1 up │ │ │ to the maximum tuple size, the instruction i_fast_element/2 will be │ │ │ produced, otherwise the instruction i_element/4 will be produced. │ │ │ The corresponding specific instructions are:

    i_fast_element xy j? I d
    │ │ │  i_element xy j? s d

    The i_fast_element/2 instruction is faster because the tuple is │ │ │ already an untagged integer. It also knows that the index is at least │ │ │ 1, so it does not have to test for that. The i_element/4 │ │ │ instruction will have to fetch the index from a register, test that it │ │ │ is an integer, and untag the integer.

    At the time of writing, all generators functions were defined in files │ │ │ named generators.tab in several directories (in the same directories │ │ │ as the predicates.tab files).

    It is outside the scope of this document to describe in detail how │ │ │ generator functions are written, but here is the implementation of │ │ │ -element():

    gen.element(Fail, Index, Tuple, Dst) {
    │ │ │ +element():

    gen.element(Fail, Index, Tuple, Dst) {
    │ │ │      BeamOp* op;
    │ │ │  
    │ │ │ -    $NewBeamOp(S, op);
    │ │ │ +    $NewBeamOp(S, op);
    │ │ │  
    │ │ │ -    if (Index.type == TAG_i && Index.val > 0 &&
    │ │ │ +    if (Index.type == TAG_i && Index.val > 0 &&
    │ │ │          Index.val <= ERTS_MAX_TUPLE_SIZE &&
    │ │ │ -        (Tuple.type == TAG_x || Tuple.type == TAG_y)) {
    │ │ │ -        $BeamOpNameArity(op, i_fast_element, 4);
    │ │ │ -        op->a[0] = Tuple;
    │ │ │ -        op->a[1] = Fail;
    │ │ │ -        op->a[2].type = TAG_u;
    │ │ │ -        op->a[2].val = Index.val;
    │ │ │ -        op->a[3] = Dst;
    │ │ │ -    } else {
    │ │ │ -        $BeamOpNameArity(op, i_element, 4);
    │ │ │ -        op->a[0] = Tuple;
    │ │ │ -        op->a[1] = Fail;
    │ │ │ -        op->a[2] = Index;
    │ │ │ -        op->a[3] = Dst;
    │ │ │ -    }
    │ │ │ +        (Tuple.type == TAG_x || Tuple.type == TAG_y)) {
    │ │ │ +        $BeamOpNameArity(op, i_fast_element, 4);
    │ │ │ +        op->a[0] = Tuple;
    │ │ │ +        op->a[1] = Fail;
    │ │ │ +        op->a[2].type = TAG_u;
    │ │ │ +        op->a[2].val = Index.val;
    │ │ │ +        op->a[3] = Dst;
    │ │ │ +    } else {
    │ │ │ +        $BeamOpNameArity(op, i_element, 4);
    │ │ │ +        op->a[0] = Tuple;
    │ │ │ +        op->a[1] = Fail;
    │ │ │ +        op->a[2] = Index;
    │ │ │ +        op->a[3] = Dst;
    │ │ │ +    }
    │ │ │  
    │ │ │      return op;
    │ │ │ -}

    The gen. prefix tells beam_makeops that this function is a │ │ │ +}

    The gen. prefix tells beam_makeops that this function is a │ │ │ generator. Without the prefix, it would have been interpreted as the │ │ │ implementation of an instruction (described in Defining the │ │ │ implementation).

    Generator functions have a magic variabled called S, which is a │ │ │ pointer to a state struct. In the example, S is used in the invocation │ │ │ of the NewBeamOp macro.

    │ │ │ │ │ │ │ │ │ @@ -814,473 +814,473 @@ │ │ │ msg_instrs.tab │ │ │ select_instrs.tab │ │ │ trace_instrs.tab

    There is also a file that only contains macro definitions:

    macros.tab

    The syntax of each file is similar to C code. In fact, most of │ │ │ the contents is C code, interspersed with macro invocations.

    To allow Emacs to auto-indent the code, each file starts with the │ │ │ following line:

    // -*- c -*-

    To avoid messing up the indentation, all comments are written │ │ │ as C++ style comments (//) instead of #. Note that a comment │ │ │ must start at the beginning of a line.

    The meat of an instruction definition file are macro definitions. │ │ │ -We have seen this macro definition before:

    move(Src, Dst) {
    │ │ │ +We have seen this macro definition before:

    move(Src, Dst) {
    │ │ │      $Dst = $Src;
    │ │ │ -}

    A macro definitions must start at the beginning of the line (no spaces │ │ │ +}

    A macro definitions must start at the beginning of the line (no spaces │ │ │ allowed), the opening curly bracket must be on the same line, and the │ │ │ finishing curly bracket must be at the beginning of a line. It is │ │ │ recommended that the macro body is properly indented.

    As a convention, the macro arguments in the head all start with an │ │ │ uppercase letter. In the body, the macro arguments can be expanded │ │ │ by preceding them with $.

    A macro definition whose name and arity matches a family of │ │ │ specific instructions is assumed to be the implementation of that │ │ │ instruction.

    A macro can also be invoked from within another macro. For example, │ │ │ move_deallocate_return/2 avoids repeating code by invoking │ │ │ -$deallocate_return() as a macro:

    move_deallocate_return(Src, Deallocate) {
    │ │ │ -    x(0) = $Src;
    │ │ │ -    $deallocate_return($Deallocate);
    │ │ │ -}

    Here is the definition of deallocate_return/1:

    deallocate_return(Deallocate) {
    │ │ │ +$deallocate_return() as a macro:

    move_deallocate_return(Src, Deallocate) {
    │ │ │ +    x(0) = $Src;
    │ │ │ +    $deallocate_return($Deallocate);
    │ │ │ +}

    Here is the definition of deallocate_return/1:

    deallocate_return(Deallocate) {
    │ │ │      //| -no_next
    │ │ │      int words_to_pop = $Deallocate;
    │ │ │ -    SET_I((BeamInstr *) cp_val(*E));
    │ │ │ -    E = ADD_BYTE_OFFSET(E, words_to_pop);
    │ │ │ -    CHECK_TERM(x(0));
    │ │ │ +    SET_I((BeamInstr *) cp_val(*E));
    │ │ │ +    E = ADD_BYTE_OFFSET(E, words_to_pop);
    │ │ │ +    CHECK_TERM(x(0));
    │ │ │      DispatchReturn;
    │ │ │ -}

    The expanded code for move_deallocate_return will look this:

    OpCase(move_deallocate_return_cQ):
    │ │ │ -{
    │ │ │ -  x(0) = I[1];
    │ │ │ -  do {
    │ │ │ -    int words_to_pop = Qb(BeamExtraData(I[0]));
    │ │ │ -    SET_I((BeamInstr *) cp_val(*E));
    │ │ │ -    E = ADD_BYTE_OFFSET(E, words_to_pop);
    │ │ │ -    CHECK_TERM(x(0));
    │ │ │ +}

    The expanded code for move_deallocate_return will look this:

    OpCase(move_deallocate_return_cQ):
    │ │ │ +{
    │ │ │ +  x(0) = I[1];
    │ │ │ +  do {
    │ │ │ +    int words_to_pop = Qb(BeamExtraData(I[0]));
    │ │ │ +    SET_I((BeamInstr *) cp_val(*E));
    │ │ │ +    E = ADD_BYTE_OFFSET(E, words_to_pop);
    │ │ │ +    CHECK_TERM(x(0));
    │ │ │      DispatchReturn;
    │ │ │ -  } while (0);
    │ │ │ -}

    When expanding macros, beam_makeops wraps the expansion in a │ │ │ + } while (0); │ │ │ +}

    When expanding macros, beam_makeops wraps the expansion in a │ │ │ do/while wrapper unless beam_makeops can clearly see that no │ │ │ wrapper is needed. In this case, the wrapper is needed.

    Note that arguments for macros cannot be complex expressions, because │ │ │ the arguments are split on ,. For example, the following would │ │ │ not work because beam_makeops would split the expression into │ │ │ -two arguments:

    $deallocate_return(get_deallocation(y, $Deallocate));

    Code generation directives

    Within macro definitions, // comments are in general not treated │ │ │ +two arguments:

    $deallocate_return(get_deallocation(y, $Deallocate));

    Code generation directives

    Within macro definitions, // comments are in general not treated │ │ │ specially. They will be copied to the file with the generated code │ │ │ along with the rest of code in the body.

    However, there is an exception. Within a macro definition, a line that │ │ │ starts with whitespace followed by //| is treated specially. The │ │ │ rest of the line is assumed to contain directives to control code │ │ │ generation.

    Currently, two code generation directives are recognized:

    The -no_prefetch directive

    To see what -no_prefetch does, let's first look at the default code │ │ │ -generation. Here is the code generated for move_cx:

    OpCase(move_cx):
    │ │ │ -{
    │ │ │ -  BeamInstr next_pf = BeamCodeAddr(I[2]);
    │ │ │ -  xb(BeamExtraData(I[0])) = I[1];
    │ │ │ +generation.  Here is the code generated for move_cx:

    OpCase(move_cx):
    │ │ │ +{
    │ │ │ +  BeamInstr next_pf = BeamCodeAddr(I[2]);
    │ │ │ +  xb(BeamExtraData(I[0])) = I[1];
    │ │ │    I += 2;
    │ │ │ -  ASSERT(VALID_INSTR(next_pf));
    │ │ │ -  GotoPF(next_pf);
    │ │ │ -}

    Note that the very first thing done is to fetch the address to the │ │ │ + ASSERT(VALID_INSTR(next_pf)); │ │ │ + GotoPF(next_pf); │ │ │ +}

    Note that the very first thing done is to fetch the address to the │ │ │ next instruction. The reason is that it usually improves performance.

    Just as a demonstration, we can add a -no_prefetch directive to │ │ │ -the move/2 instruction:

    move(Src, Dst) {
    │ │ │ +the move/2 instruction:

    move(Src, Dst) {
    │ │ │      //| -no_prefetch
    │ │ │      $Dst = $Src;
    │ │ │ -}

    We can see that the prefetch is no longer done:

    OpCase(move_cx):
    │ │ │ -{
    │ │ │ -  xb(BeamExtraData(I[0])) = I[1];
    │ │ │ +}

    We can see that the prefetch is no longer done:

    OpCase(move_cx):
    │ │ │ +{
    │ │ │ +  xb(BeamExtraData(I[0])) = I[1];
    │ │ │    I += 2;
    │ │ │ -  ASSERT(VALID_INSTR(*I));
    │ │ │ -  Goto(*I);
    │ │ │ -}

    When would we want to turn off the prefetch in practice?

    In instructions that will not always execute the next instruction. │ │ │ -For example:

    is_atom(Fail, Src) {
    │ │ │ -    if (is_not_atom($Src)) {
    │ │ │ -        $FAIL($Fail);
    │ │ │ -    }
    │ │ │ -}
    │ │ │ +  ASSERT(VALID_INSTR(*I));
    │ │ │ +  Goto(*I);
    │ │ │ +}

    When would we want to turn off the prefetch in practice?

    In instructions that will not always execute the next instruction. │ │ │ +For example:

    is_atom(Fail, Src) {
    │ │ │ +    if (is_not_atom($Src)) {
    │ │ │ +        $FAIL($Fail);
    │ │ │ +    }
    │ │ │ +}
    │ │ │  
    │ │ │  // From macros.tab
    │ │ │ -FAIL(Fail) {
    │ │ │ +FAIL(Fail) {
    │ │ │      //| -no_prefetch
    │ │ │ -    $SET_I_REL($Fail);
    │ │ │ -    Goto(*I);
    │ │ │ -}

    is_atom/2 may either execute the next instruction (if the second │ │ │ -operand is an atom) or branch to the failure label.

    The generated code looks like this:

    OpCase(is_atom_fx):
    │ │ │ -{
    │ │ │ -  if (is_not_atom(xb(I[1]))) {
    │ │ │ -    ASSERT(VALID_INSTR(*(I + (fb(BeamExtraData(I[0]))) + 0)));
    │ │ │ -    I += fb(BeamExtraData(I[0])) + 0;;
    │ │ │ -    Goto(*I);;
    │ │ │ -  }
    │ │ │ +    $SET_I_REL($Fail);
    │ │ │ +    Goto(*I);
    │ │ │ +}

    is_atom/2 may either execute the next instruction (if the second │ │ │ +operand is an atom) or branch to the failure label.

    The generated code looks like this:

    OpCase(is_atom_fx):
    │ │ │ +{
    │ │ │ +  if (is_not_atom(xb(I[1]))) {
    │ │ │ +    ASSERT(VALID_INSTR(*(I + (fb(BeamExtraData(I[0]))) + 0)));
    │ │ │ +    I += fb(BeamExtraData(I[0])) + 0;;
    │ │ │ +    Goto(*I);;
    │ │ │ +  }
    │ │ │    I += 2;
    │ │ │ -  ASSERT(VALID_INSTR(*I));
    │ │ │ -  Goto(*I);
    │ │ │ -}
    The -no_next directive

    Next we will look at when the -no_next directive can be used. Here │ │ │ -is the jump/1 instruction:

    jump(Fail) {
    │ │ │ -    $JUMP($Fail);
    │ │ │ -}
    │ │ │ +  ASSERT(VALID_INSTR(*I));
    │ │ │ +  Goto(*I);
    │ │ │ +}
    The -no_next directive

    Next we will look at when the -no_next directive can be used. Here │ │ │ +is the jump/1 instruction:

    jump(Fail) {
    │ │ │ +    $JUMP($Fail);
    │ │ │ +}
    │ │ │  
    │ │ │  // From macros.tab
    │ │ │ -JUMP(Fail) {
    │ │ │ +JUMP(Fail) {
    │ │ │      //| -no_next
    │ │ │ -    $SET_I_REL($Fail);
    │ │ │ -    Goto(*I);
    │ │ │ -}

    The generated code looks like this:

    OpCase(jump_f):
    │ │ │ -{
    │ │ │ -  ASSERT(VALID_INSTR(*(I + (fb(BeamExtraData(I[0]))) + 0)));
    │ │ │ -  I += fb(BeamExtraData(I[0])) + 0;;
    │ │ │ -  Goto(*I);;
    │ │ │ -}

    If we remove the -no_next directive, the code would look like this:

    OpCase(jump_f):
    │ │ │ -{
    │ │ │ -  BeamInstr next_pf = BeamCodeAddr(I[1]);
    │ │ │ -  ASSERT(VALID_INSTR(*(I + (fb(BeamExtraData(I[0]))) + 0)));
    │ │ │ -  I += fb(BeamExtraData(I[0])) + 0;;
    │ │ │ -  Goto(*I);;
    │ │ │ +    $SET_I_REL($Fail);
    │ │ │ +    Goto(*I);
    │ │ │ +}

    The generated code looks like this:

    OpCase(jump_f):
    │ │ │ +{
    │ │ │ +  ASSERT(VALID_INSTR(*(I + (fb(BeamExtraData(I[0]))) + 0)));
    │ │ │ +  I += fb(BeamExtraData(I[0])) + 0;;
    │ │ │ +  Goto(*I);;
    │ │ │ +}

    If we remove the -no_next directive, the code would look like this:

    OpCase(jump_f):
    │ │ │ +{
    │ │ │ +  BeamInstr next_pf = BeamCodeAddr(I[1]);
    │ │ │ +  ASSERT(VALID_INSTR(*(I + (fb(BeamExtraData(I[0]))) + 0)));
    │ │ │ +  I += fb(BeamExtraData(I[0])) + 0;;
    │ │ │ +  Goto(*I);;
    │ │ │    I += 1;
    │ │ │ -  ASSERT(VALID_INSTR(next_pf));
    │ │ │ -  GotoPF(next_pf);
    │ │ │ -}

    In the end, the C compiler will probably optimize this code to the │ │ │ + ASSERT(VALID_INSTR(next_pf)); │ │ │ + GotoPF(next_pf); │ │ │ +}

    In the end, the C compiler will probably optimize this code to the │ │ │ same native code as the first version, but the first version is certainly │ │ │ much easier to read for human readers.

    Macros in the macros.tab file

    The file macros.tab contains many useful macros. When implementing │ │ │ new instructions it is good practice to look through macros.tab to │ │ │ see if any of existing macros can be used rather than re-inventing │ │ │ the wheel.

    We will describe a few of the most useful macros here.

    The GC_REGEXP definition

    The following line defines a regular expression that will recognize │ │ │ a call to a function that does a garbage collection:

     GC_REGEXP=erts_garbage_collect|erts_gc|GcBifFunction;

    The purpose is that beam_makeops can verify that an instruction │ │ │ that does a garbage collection and has an d operand uses the │ │ │ $REFRESH_GEN_DEST() macro.

    If you need to define a new function that does garbage collection, │ │ │ you should give it the prefix erts_gc_. If that is not possible │ │ │ you should update the regular expression so that it will match your │ │ │ -new function.

    FAIL(Fail)

    Branch to $Fail. Will suppress prefetch (-no_prefetch). Typical use:

    is_nonempty_list(Fail, Src) {
    │ │ │ -    if (is_not_list($Src)) {
    │ │ │ -        $FAIL($Fail);
    │ │ │ -    }
    │ │ │ -}
    JUMP(Fail)

    Branch to $Fail. Suppresses generation of dispatch of the next │ │ │ -instruction (-no_next). Typical use:

    jump(Fail) {
    │ │ │ -    $JUMP($Fail);
    │ │ │ -}
    GC_TEST(NeedStack, NeedHeap, Live)

    $GC_TEST(NeedStack, NeedHeap, Live) tests that given amount of │ │ │ +new function.

    FAIL(Fail)

    Branch to $Fail. Will suppress prefetch (-no_prefetch). Typical use:

    is_nonempty_list(Fail, Src) {
    │ │ │ +    if (is_not_list($Src)) {
    │ │ │ +        $FAIL($Fail);
    │ │ │ +    }
    │ │ │ +}
    JUMP(Fail)

    Branch to $Fail. Suppresses generation of dispatch of the next │ │ │ +instruction (-no_next). Typical use:

    jump(Fail) {
    │ │ │ +    $JUMP($Fail);
    │ │ │ +}
    GC_TEST(NeedStack, NeedHeap, Live)

    $GC_TEST(NeedStack, NeedHeap, Live) tests that given amount of │ │ │ stack space and heap space is available. If not it will do a │ │ │ -garbage collection. Typical use:

    test_heap(Nh, Live) {
    │ │ │ -    $GC_TEST(0, $Nh, $Live);
    │ │ │ -}
    AH(NeedStack, NeedHeap, Live)

    AH(NeedStack, NeedHeap, Live) allocates a stack frame and │ │ │ +garbage collection. Typical use:

    test_heap(Nh, Live) {
    │ │ │ +    $GC_TEST(0, $Nh, $Live);
    │ │ │ +}
    AH(NeedStack, NeedHeap, Live)

    AH(NeedStack, NeedHeap, Live) allocates a stack frame and │ │ │ optionally additional heap space.

    Pre-defined macros and variables

    beam_makeops defines several built-in macros and pre-bound variables.

    The NEXT_INSTRUCTION pre-bound variable

    The NEXT_INSTRUCTION is a pre-bound variable that is available in │ │ │ -all instructions. It expands to the address of the next instruction.

    Here is an example:

    i_call(CallDest) {
    │ │ │ +all instructions.  It expands to the address of the next instruction.

    Here is an example:

    i_call(CallDest) {
    │ │ │      //| -no_next
    │ │ │ -    $SAVE_CONTINUATION_POINTER($NEXT_INSTRUCTION);
    │ │ │ -    $DISPATCH_REL($CallDest);
    │ │ │ -}

    When calling a function, the return address is first stored in E[0] │ │ │ + $SAVE_CONTINUATION_POINTER($NEXT_INSTRUCTION); │ │ │ + $DISPATCH_REL($CallDest); │ │ │ +}

    When calling a function, the return address is first stored in E[0] │ │ │ (using the $SAVE_CONTINUATION_POINTER() macro), and then control is │ │ │ -transferred to the callee. Here is the generated code:

    OpCase(i_call_f):
    │ │ │ -{
    │ │ │ -    ASSERT(VALID_INSTR(*(I+2)));
    │ │ │ -    *E = (BeamInstr) (I+2);;
    │ │ │ +transferred to the callee.  Here is the generated code:

    OpCase(i_call_f):
    │ │ │ +{
    │ │ │ +    ASSERT(VALID_INSTR(*(I+2)));
    │ │ │ +    *E = (BeamInstr) (I+2);;
    │ │ │  
    │ │ │      /* ... dispatch code intentionally left out ... */
    │ │ │ -}

    We can see that that $NEXT_INSTRUCTION has been expanded to I+2. │ │ │ +}

    We can see that that $NEXT_INSTRUCTION has been expanded to I+2. │ │ │ That makes sense since the size of the i_call_f/1 instruction is │ │ │ two words.

    The IP_ADJUSTMENT pre-bound variable

    $IP_ADJUSTMENT is usually 0. In a few combined instructions │ │ │ (described below) it can be non-zero. It is used like this │ │ │ -in macros.tab:

    SET_I_REL(Offset) {
    │ │ │ -    ASSERT(VALID_INSTR(*(I + ($Offset) + $IP_ADJUSTMENT)));
    │ │ │ +in macros.tab:

    SET_I_REL(Offset) {
    │ │ │ +    ASSERT(VALID_INSTR(*(I + ($Offset) + $IP_ADJUSTMENT)));
    │ │ │      I += $Offset + $IP_ADJUSTMENT;
    │ │ │ -}

    Avoid using IP_ADJUSTMENT directly. Use SET_I_REL() or │ │ │ +}

    Avoid using IP_ADJUSTMENT directly. Use SET_I_REL() or │ │ │ one of the macros that invoke such as FAIL() or JUMP() │ │ │ defined in macros.tab.

    Pre-defined macro functions

    The IF() macro

    $IF(Expr, IfTrue, IfFalse) evaluates Expr, which must be a valid │ │ │ Perl expression (which for simple numeric expressions have the same │ │ │ syntax as C). If Expr evaluates to 0, the entire IF() expression will be │ │ │ replaced with IfFalse, otherwise it will be replaced with IfTrue.

    See the description of OPERAND_POSITION() for an example.

    The OPERAND_POSITION() macro

    $OPERAND_POSITION(Expr) returns the position for Expr, if │ │ │ Expr is an operand that is not packed. The first operand is │ │ │ -at position 1.

    Returns 0 otherwise.

    This macro could be used like this in order to share code:

    FAIL(Fail) {
    │ │ │ +at position 1.

    Returns 0 otherwise.

    This macro could be used like this in order to share code:

    FAIL(Fail) {
    │ │ │      //| -no_prefetch
    │ │ │ -    $IF($OPERAND_POSITION($Fail) == 1 && $IP_ADJUSTMENT == 0,
    │ │ │ +    $IF($OPERAND_POSITION($Fail) == 1 && $IP_ADJUSTMENT == 0,
    │ │ │          goto common_jump,
    │ │ │ -        $DO_JUMP($Fail));
    │ │ │ -}
    │ │ │ +        $DO_JUMP($Fail));
    │ │ │ +}
    │ │ │  
    │ │ │ -DO_JUMP(Fail) {
    │ │ │ -    $SET_I_REL($Fail);
    │ │ │ -    Goto(*I));
    │ │ │ -}
    │ │ │ +DO_JUMP(Fail) {
    │ │ │ +    $SET_I_REL($Fail);
    │ │ │ +    Goto(*I));
    │ │ │ +}
    │ │ │  
    │ │ │  // In beam_emu.c:
    │ │ │  common_jump:
    │ │ │ -   I += I[1];
    │ │ │ -   Goto(*I));

    The $REFRESH_GEN_DEST() macro

    When a specific instruction has a d operand, early during execution │ │ │ + I += I[1]; │ │ │ + Goto(*I));

    The $REFRESH_GEN_DEST() macro

    When a specific instruction has a d operand, early during execution │ │ │ of the instruction, a pointer will be initialized to point to the X or │ │ │ Y register in question.

    If there is a garbage collection before the result is stored, │ │ │ the stack will move and if the d operand referred to a Y │ │ │ register, the pointer will no longer be valid. (Y registers are │ │ │ stored on the stack.)

    In those circumstances, $REFRESH_GEN_DEST() must be invoked │ │ │ to set up the pointer again. beam_makeops will notice │ │ │ if there is a call to a function that does a garbage collection and │ │ │ $REFRESH_GEN_DEST() is not called.

    Here is a complete example. The new_map instruction is defined │ │ │ -like this:

    new_map d t I

    It is implemented like this:

    new_map(Dst, Live, N) {
    │ │ │ +like this:

    new_map d t I

    It is implemented like this:

    new_map(Dst, Live, N) {
    │ │ │      Eterm res;
    │ │ │  
    │ │ │      HEAVY_SWAPOUT;
    │ │ │ -    res = erts_gc_new_map(c_p, reg, $Live, $N, $NEXT_INSTRUCTION);
    │ │ │ +    res = erts_gc_new_map(c_p, reg, $Live, $N, $NEXT_INSTRUCTION);
    │ │ │      HEAVY_SWAPIN;
    │ │ │ -    $REFRESH_GEN_DEST();
    │ │ │ +    $REFRESH_GEN_DEST();
    │ │ │      $Dst = res;
    │ │ │ -    $NEXT($NEXT_INSTRUCTION+$N);
    │ │ │ -}

    If we have forgotten the $REFRESH_GEN_DEST() there would be a message │ │ │ -similar to this:

    pointer to destination register is invalid after GC -- use $REFRESH_GEN_DEST()
    │ │ │ -... from the body of new_map at beam/map_instrs.tab(30)

    Variable number of operands

    Here follows an example of how to handle an instruction with a variable number │ │ │ + $NEXT($NEXT_INSTRUCTION+$N); │ │ │ +}

    If we have forgotten the $REFRESH_GEN_DEST() there would be a message │ │ │ +similar to this:

    pointer to destination register is invalid after GC -- use $REFRESH_GEN_DEST()
    │ │ │ +... from the body of new_map at beam/map_instrs.tab(30)

    Variable number of operands

    Here follows an example of how to handle an instruction with a variable number │ │ │ of operands for the interpreter. Here is the instruction definition in emu/ops.tab:

    put_tuple2 xy I *

    For the interpreter, the * is optional, because it does not effect code generation │ │ │ in any way. However, it is recommended to include it to make it clear for human readers │ │ │ that there is a variable number of operands.

    Use the $NEXT_INSTRUCTION macro to obtain a pointer to the first of the variable │ │ │ -operands.

    Here is the implementation:

    put_tuple2(Dst, Arity) {
    │ │ │ +operands.

    Here is the implementation:

    put_tuple2(Dst, Arity) {
    │ │ │  Eterm* hp = HTOP;
    │ │ │  Eterm arity = $Arity;
    │ │ │ -Eterm* dst_ptr = &($Dst);
    │ │ │ +Eterm* dst_ptr = &($Dst);
    │ │ │  
    │ │ │  //| -no_next
    │ │ │ -ASSERT(arity != 0);
    │ │ │ -*hp++ = make_arityval(arity);
    │ │ │ +ASSERT(arity != 0);
    │ │ │ +*hp++ = make_arityval(arity);
    │ │ │  
    │ │ │  /*
    │ │ │   * The $NEXT_INSTRUCTION macro points just beyond the fixed
    │ │ │   * operands. In this case it points to the descriptor of
    │ │ │   * the first element to be put into the tuple.
    │ │ │   */
    │ │ │  I = $NEXT_INSTRUCTION;
    │ │ │ -do {
    │ │ │ +do {
    │ │ │      Eterm term = *I++;
    │ │ │ -    switch (loader_tag(term)) {
    │ │ │ +    switch (loader_tag(term)) {
    │ │ │      case LOADER_X_REG:
    │ │ │ -    *hp++ = x(loader_x_reg_index(term));
    │ │ │ +    *hp++ = x(loader_x_reg_index(term));
    │ │ │      break;
    │ │ │      case LOADER_Y_REG:
    │ │ │ -    *hp++ = y(loader_y_reg_index(term));
    │ │ │ +    *hp++ = y(loader_y_reg_index(term));
    │ │ │      break;
    │ │ │      default:
    │ │ │      *hp++ = term;
    │ │ │      break;
    │ │ │ -    }
    │ │ │ -} while (--arity != 0);
    │ │ │ -*dst_ptr = make_tuple(HTOP);
    │ │ │ +    }
    │ │ │ +} while (--arity != 0);
    │ │ │ +*dst_ptr = make_tuple(HTOP);
    │ │ │  HTOP = hp;
    │ │ │ -ASSERT(VALID_INSTR(* (Eterm *)I));
    │ │ │ -Goto(*I);
    │ │ │ -}

    Combined instructions

    Problem: For frequently executed instructions we want to use │ │ │ +ASSERT(VALID_INSTR(* (Eterm *)I)); │ │ │ +Goto(*I); │ │ │ +}

    Combined instructions

    Problem: For frequently executed instructions we want to use │ │ │ "fast" operands types such as x and y, as opposed to s or S. │ │ │ To avoid an explosion in code size, we want to share most of the │ │ │ implementation between the instructions. Here are the specific │ │ │ instructions for i_increment/5:

    i_increment r W t d
    │ │ │  i_increment x W t d
    │ │ │ -i_increment y W t d

    The i_increment instruction is implemented like this:

    i_increment(Source, IncrementVal, Live, Dst) {
    │ │ │ +i_increment y W t d

    The i_increment instruction is implemented like this:

    i_increment(Source, IncrementVal, Live, Dst) {
    │ │ │      Eterm increment_reg_source = $Source;
    │ │ │      Eterm increment_val = $IncrementVal;
    │ │ │      Uint live;
    │ │ │      Eterm result;
    │ │ │  
    │ │ │ -    if (ERTS_LIKELY(is_small(increment_reg_val))) {
    │ │ │ -        Sint i = signed_val(increment_reg_val) + increment_val;
    │ │ │ -        if (ERTS_LIKELY(IS_SSMALL(i))) {
    │ │ │ -            $Dst = make_small(i);
    │ │ │ -            $NEXT0();
    │ │ │ -        }
    │ │ │ -    }
    │ │ │ +    if (ERTS_LIKELY(is_small(increment_reg_val))) {
    │ │ │ +        Sint i = signed_val(increment_reg_val) + increment_val;
    │ │ │ +        if (ERTS_LIKELY(IS_SSMALL(i))) {
    │ │ │ +            $Dst = make_small(i);
    │ │ │ +            $NEXT0();
    │ │ │ +        }
    │ │ │ +    }
    │ │ │      live = $Live;
    │ │ │      HEAVY_SWAPOUT;
    │ │ │ -    reg[live] = increment_reg_val;
    │ │ │ -    reg[live+1] = make_small(increment_val);
    │ │ │ -    result = erts_gc_mixed_plus(c_p, reg, live);
    │ │ │ +    reg[live] = increment_reg_val;
    │ │ │ +    reg[live+1] = make_small(increment_val);
    │ │ │ +    result = erts_gc_mixed_plus(c_p, reg, live);
    │ │ │      HEAVY_SWAPIN;
    │ │ │ -    ERTS_HOLE_CHECK(c_p);
    │ │ │ -    if (ERTS_LIKELY(is_value(result))) {
    │ │ │ -        $REFRESH_GEN_DEST();
    │ │ │ +    ERTS_HOLE_CHECK(c_p);
    │ │ │ +    if (ERTS_LIKELY(is_value(result))) {
    │ │ │ +        $REFRESH_GEN_DEST();
    │ │ │          $Dst = result;
    │ │ │ -        $NEXT0();
    │ │ │ -    }
    │ │ │ -    ASSERT(c_p->freason != BADMATCH || is_value(c_p->fvalue));
    │ │ │ +        $NEXT0();
    │ │ │ +    }
    │ │ │ +    ASSERT(c_p->freason != BADMATCH || is_value(c_p->fvalue));
    │ │ │      goto find_func_info;
    │ │ │ -}

    There will be three almost identical copies of the code. Given the │ │ │ +}

    There will be three almost identical copies of the code. Given the │ │ │ size of the code, that could be too high cost to pay.

    To avoid the three copies of the code, we could use only one specific │ │ │ instruction:

    i_increment S W t d

    (The same implementation as above will work.)

    That reduces the code size, but is slower because S means that │ │ │ there will be extra code to test whether the operand refers to an X │ │ │ register or a Y register.

    Solution: We can use "combined instructions". Combined │ │ │ instructions are combined from instruction fragments. The │ │ │ bulk of the code can be shared.

    Here we will show how i_increment can be implemented as a combined │ │ │ instruction. We will show each individual fragment first, and then │ │ │ show how to connect them together. First we will need a variable that │ │ │ -we can store the value fetched from the register in:

    increment.head() {
    │ │ │ +we can store the value fetched from the register in:

    increment.head() {
    │ │ │      Eterm increment_reg_val;
    │ │ │ -}

    The name increment is the name of the group that the fragment │ │ │ +}

    The name increment is the name of the group that the fragment │ │ │ belongs to. Note that it does not need to have the same │ │ │ name as the instruction. The group name is followed by . and │ │ │ the name of the fragment. The name head is pre-defined. │ │ │ The code in it will be placed at the beginning of a block, so │ │ │ that all fragments in the group can access it.

    Next we define the fragment that will pick up the value from the │ │ │ -register from the first operand:

    increment.fetch(Src) {
    │ │ │ +register from the first operand:

    increment.fetch(Src) {
    │ │ │      increment_reg_val = $Src;
    │ │ │ -}

    We call this fragment fetch. This fragment will be duplicated three │ │ │ -times, one for each value of the first operand (r, x, and y).

    Next we define the main part of the code that do the actual incrementing.

    increment.execute(IncrementVal, Live, Dst) {
    │ │ │ +}

    We call this fragment fetch. This fragment will be duplicated three │ │ │ +times, one for each value of the first operand (r, x, and y).

    Next we define the main part of the code that do the actual incrementing.

    increment.execute(IncrementVal, Live, Dst) {
    │ │ │      Eterm increment_val = $IncrementVal;
    │ │ │      Uint live;
    │ │ │      Eterm result;
    │ │ │  
    │ │ │ -    if (ERTS_LIKELY(is_small(increment_reg_val))) {
    │ │ │ -        Sint i = signed_val(increment_reg_val) + increment_val;
    │ │ │ -        if (ERTS_LIKELY(IS_SSMALL(i))) {
    │ │ │ -            $Dst = make_small(i);
    │ │ │ -            $NEXT0();
    │ │ │ -        }
    │ │ │ -    }
    │ │ │ +    if (ERTS_LIKELY(is_small(increment_reg_val))) {
    │ │ │ +        Sint i = signed_val(increment_reg_val) + increment_val;
    │ │ │ +        if (ERTS_LIKELY(IS_SSMALL(i))) {
    │ │ │ +            $Dst = make_small(i);
    │ │ │ +            $NEXT0();
    │ │ │ +        }
    │ │ │ +    }
    │ │ │      live = $Live;
    │ │ │      HEAVY_SWAPOUT;
    │ │ │ -    reg[live] = increment_reg_val;
    │ │ │ -    reg[live+1] = make_small(increment_val);
    │ │ │ -    result = erts_gc_mixed_plus(c_p, reg, live);
    │ │ │ +    reg[live] = increment_reg_val;
    │ │ │ +    reg[live+1] = make_small(increment_val);
    │ │ │ +    result = erts_gc_mixed_plus(c_p, reg, live);
    │ │ │      HEAVY_SWAPIN;
    │ │ │ -    ERTS_HOLE_CHECK(c_p);
    │ │ │ -    if (ERTS_LIKELY(is_value(result))) {
    │ │ │ -        $REFRESH_GEN_DEST();
    │ │ │ +    ERTS_HOLE_CHECK(c_p);
    │ │ │ +    if (ERTS_LIKELY(is_value(result))) {
    │ │ │ +        $REFRESH_GEN_DEST();
    │ │ │          $Dst = result;
    │ │ │ -        $NEXT0();
    │ │ │ -    }
    │ │ │ -    ASSERT(c_p->freason != BADMATCH || is_value(c_p->fvalue));
    │ │ │ +        $NEXT0();
    │ │ │ +    }
    │ │ │ +    ASSERT(c_p->freason != BADMATCH || is_value(c_p->fvalue));
    │ │ │      goto find_func_info;
    │ │ │ -}

    We call this fragment execute. It will handle the three remaining │ │ │ +}

    We call this fragment execute. It will handle the three remaining │ │ │ operands (W t d). There will only be one copy of this fragment.

    Now that we have defined the fragments, we need to inform │ │ │ beam_makeops how they should be connected:

    i_increment := increment.fetch.execute;

    To the left of the := is the name of the specific instruction that │ │ │ should be implemented by the fragments, in this case i_increment. │ │ │ To the right of := is the name of the group with the fragments, │ │ │ followed by a .. Then the name of the fragments in the group are │ │ │ listed in the order they should be executed. Note that the head │ │ │ fragment is not listed.

    The line ends in ; (to avoid messing up the indentation in Emacs).

    (Note that in practice the := line is usually placed before the │ │ │ -fragments.)

    The generated code looks like this:

    {
    │ │ │ +fragments.)

    The generated code looks like this:

    {
    │ │ │    Eterm increment_reg_val;
    │ │ │ -  OpCase(i_increment_rWtd):
    │ │ │ -  {
    │ │ │ -    increment_reg_val = r(0);
    │ │ │ -  }
    │ │ │ +  OpCase(i_increment_rWtd):
    │ │ │ +  {
    │ │ │ +    increment_reg_val = r(0);
    │ │ │ +  }
    │ │ │    goto increment__execute;
    │ │ │  
    │ │ │ -  OpCase(i_increment_xWtd):
    │ │ │ -  {
    │ │ │ -    increment_reg_val = xb(BeamExtraData(I[0]));
    │ │ │ -  }
    │ │ │ +  OpCase(i_increment_xWtd):
    │ │ │ +  {
    │ │ │ +    increment_reg_val = xb(BeamExtraData(I[0]));
    │ │ │ +  }
    │ │ │    goto increment__execute;
    │ │ │  
    │ │ │ -  OpCase(i_increment_yWtd):
    │ │ │ -  {
    │ │ │ -    increment_reg_val = yb(BeamExtraData(I[0]));
    │ │ │ -  }
    │ │ │ +  OpCase(i_increment_yWtd):
    │ │ │ +  {
    │ │ │ +    increment_reg_val = yb(BeamExtraData(I[0]));
    │ │ │ +  }
    │ │ │    goto increment__execute;
    │ │ │  
    │ │ │    increment__execute:
    │ │ │ -  {
    │ │ │ -    // Here follows the code from increment.execute()
    │ │ │ +  {
    │ │ │ +    // Here follows the code from increment.execute()
    │ │ │      .
    │ │ │      .
    │ │ │      .
    │ │ │ -}
    Some notes about combined instructions

    The operands that are different must be at │ │ │ +}

    Some notes about combined instructions

    The operands that are different must be at │ │ │ the beginning of the instruction. All operands in the last │ │ │ fragment must have the same operands in all variants of │ │ │ the specific instruction.

    As an example, the following specific instructions cannot be │ │ │ implemented as a combined instruction:

    i_times j? t x x d
    │ │ │  i_times j? t x y d
    │ │ │  i_times j? t s s d

    We would have to change the order of the operands so that the │ │ │ two operands that are different are placed first:

    i_times x x j? t d
    │ │ │  i_times x y j? t d
    │ │ │  i_times s s j? t d

    We can then define:

    i_times := times.fetch.execute;
    │ │ │  
    │ │ │ -times.head {
    │ │ │ +times.head {
    │ │ │      Eterm op1, op2;
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -times.fetch(Src1, Src2) {
    │ │ │ +times.fetch(Src1, Src2) {
    │ │ │      op1 = $Src1;
    │ │ │      op2 = $Src2;
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -times.execute(Fail, Live, Dst) {
    │ │ │ +times.execute(Fail, Live, Dst) {
    │ │ │      // Multiply op1 and op2.
    │ │ │      .
    │ │ │      .
    │ │ │      .
    │ │ │ -}

    Several instructions can share a group. As an example, the following │ │ │ +}

    Several instructions can share a group. As an example, the following │ │ │ instructions have different names, but in the end they all create a │ │ │ binary. The last two operands are common for all of them:

    i_bs_init_fail       xy j? t? x
    │ │ │  i_bs_init_fail_heap s I j? t? x
    │ │ │  i_bs_init                W t? x
    │ │ │  i_bs_init_heap         W I t? x

    The instructions are defined like this (formatted with extra │ │ │ spaces for clarity):

    i_bs_init_fail_heap := bs_init . fail_heap . verify . execute;
    │ │ │  i_bs_init_fail      := bs_init . fail      . verify . execute;
    │ │ │  i_bs_init           := bs_init .           .  plain . execute;
    │ │ │  i_bs_init_heap      := bs_init .               heap . execute;

    Note that the first two instruction have three fragments, while the │ │ │ -other two only have two fragments. Here are the fragments:

    bs_init_bits.head() {
    │ │ │ +other two only have two fragments.  Here are the fragments:

    bs_init_bits.head() {
    │ │ │      Eterm num_bits_term;
    │ │ │      Uint num_bits;
    │ │ │      Uint alloc;
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -bs_init_bits.plain(NumBits) {
    │ │ │ +bs_init_bits.plain(NumBits) {
    │ │ │      num_bits = $NumBits;
    │ │ │      alloc = 0;
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -bs_init_bits.heap(NumBits, Alloc) {
    │ │ │ +bs_init_bits.heap(NumBits, Alloc) {
    │ │ │      num_bits = $NumBits;
    │ │ │      alloc = $Alloc;
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -bs_init_bits.fail(NumBitsTerm) {
    │ │ │ +bs_init_bits.fail(NumBitsTerm) {
    │ │ │      num_bits_term = $NumBitsTerm;
    │ │ │      alloc = 0;
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -bs_init_bits.fail_heap(NumBitsTerm, Alloc) {
    │ │ │ +bs_init_bits.fail_heap(NumBitsTerm, Alloc) {
    │ │ │      num_bits_term = $NumBitsTerm;
    │ │ │      alloc = $Alloc;
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -bs_init_bits.verify(Fail) {
    │ │ │ +bs_init_bits.verify(Fail) {
    │ │ │      // Verify the num_bits_term, fail using $FAIL
    │ │ │      // if there is a problem.
    │ │ │  .
    │ │ │  .
    │ │ │  .
    │ │ │ -}
    │ │ │ +}
    │ │ │  
    │ │ │ -bs_init_bits.execute(Live, Dst) {
    │ │ │ +bs_init_bits.execute(Live, Dst) {
    │ │ │     // Long complicated code to a create a binary.
    │ │ │     .
    │ │ │     .
    │ │ │     .
    │ │ │ -}

    The full definitions of those instructions can be found in bs_instrs.tab. │ │ │ +}

    The full definitions of those instructions can be found in bs_instrs.tab. │ │ │ The generated code can be found in beam_warm.h.

    │ │ │ │ │ │ │ │ │ │ │ │ Code generation for BeamAsm │ │ │

    │ │ │

    For the BeamAsm runtime system, the implementation of each instruction is defined │ │ │ by emitter functions written in C++ that emit the assembly code for each instruction. │ │ │ There is one emitter function for each family of specific instructions.

    Take for example the move instruction. In beam/asm/ops.tab there is a │ │ │ -single specific instruction for move defined like this:

    move s d

    The implementation is found in beam/asm/instr_common.cpp:

    void BeamModuleAssembler::emit_move(const ArgVal &Src, const ArgVal &Dst) {
    │ │ │ -    mov_arg(Dst, Src);
    │ │ │ -}

    The mov_arg() helper function will handle all combinations of source and destination │ │ │ -operands. For example, the instruction {move,{x,1},{y,1}} will be translated like this:

    mov rdi, qword [rbx+8]
    │ │ │ -mov qword [rsp+8], rdi

    while {move,{integer,42},{x,0}} will be translated like this:

    mov qword [rbx], 687

    It is possible to define more than one specific instruction, but there will still be │ │ │ +single specific instruction for move defined like this:

    move s d

    The implementation is found in beam/asm/instr_common.cpp:

    void BeamModuleAssembler::emit_move(const ArgVal &Src, const ArgVal &Dst) {
    │ │ │ +    mov_arg(Dst, Src);
    │ │ │ +}

    The mov_arg() helper function will handle all combinations of source and destination │ │ │ +operands. For example, the instruction {move,{x,1},{y,1}} will be translated like this:

    mov rdi, qword [rbx+8]
    │ │ │ +mov qword [rsp+8], rdi

    while {move,{integer,42},{x,0}} will be translated like this:

    mov qword [rbx], 687

    It is possible to define more than one specific instruction, but there will still be │ │ │ only one emitter function. For example:

    fload S l
    │ │ │  fload q l

    By defining fload like this, the source operand must be a X register, Y register, or │ │ │ a literal. If not, the loading will be aborted. If the instruction instead had been │ │ │ defined like this:

    fload s l

    attempting to load an invalid instruction such as {fload,{atom,clearly_bad},{fr,0}} │ │ │ would cause a crash (either at load time or when the instruction was executed).

    Regardless on how many specific instructions there are in the family, │ │ │ -only a single emit_fload() function is allowed:

    void BeamModuleAssembler::emit_fload(const ArgVal &Src, const ArgVal &Dst) {
    │ │ │ +only a single emit_fload() function is allowed:

    void BeamModuleAssembler::emit_fload(const ArgVal &Src, const ArgVal &Dst) {
    │ │ │      .
    │ │ │      .
    │ │ │      .
    │ │ │ -}

    Handling a variable number of operands

    Here follows an example of how an instruction with a variable number │ │ │ +}

    Handling a variable number of operands

    Here follows an example of how an instruction with a variable number │ │ │ of operands could be handled. One such instructions is │ │ │ -select_val/3. Here is an example how it can look like in BEAM code:

    {select_val,{x,0},
    │ │ │ -            {f,1},
    │ │ │ -            {list,[{atom,b},{f,4},{atom,a},{f,5}]}}.

    The loader will convert a {list,[...]} operand to an u operand whose │ │ │ +select_val/3. Here is an example how it can look like in BEAM code:

    {select_val,{x,0},
    │ │ │ +            {f,1},
    │ │ │ +            {list,[{atom,b},{f,4},{atom,a},{f,5}]}}.

    The loader will convert a {list,[...]} operand to an u operand whose │ │ │ value is the number of elements in the list, followed by each element in │ │ │ the list. The instruction above would be translated to the following │ │ │ -instruction:

    {select_val,{x,0},{f,1},{u,4},{atom,b},{f,4},{atom,a},{f,5}}

    A definition of a specific instruction for that instruction would look │ │ │ +instruction:

    {select_val,{x,0},{f,1},{u,4},{atom,b},{f,4},{atom,a},{f,5}}

    A definition of a specific instruction for that instruction would look │ │ │ like this:

    select_val s f I *

    The * as the last operand will make sure that the variable operands │ │ │ are passed in as a Span of ArgVal (will be std::span in C++20 onwards). │ │ │ -Here is the emitter function:

    void BeamModuleAssembler::emit_select_val(const ArgVal &Src,
    │ │ │ +Here is the emitter function:

    void BeamModuleAssembler::emit_select_val(const ArgVal &Src,
    │ │ │                                            const ArgVal &Fail,
    │ │ │                                            const ArgVal &Size,
    │ │ │ -                                          const Span<ArgVal> &args) {
    │ │ │ -    ASSERT(Size.getValue() == args.size());
    │ │ │ +                                          const Span<ArgVal> &args) {
    │ │ │ +    ASSERT(Size.getValue() == args.size());
    │ │ │         .
    │ │ │         .
    │ │ │         .
    │ │ │ -}
    │ │ │ +
    }
    │ │ │ │ │ │ │ │ │
    │ │ │
    │ │ │ │ │ │

    beam_makeops. The transformations │ │ │ used in BeamAsm are much simpler than the interpreter's, as most of the │ │ │ transformations for the interpreter are done only to eliminate the instruction │ │ │ dispatch overhead.

    Then each instruction is encoded using the C++ functions in the │ │ │ -jit/$ARCH/instr_*.cpp files. For example:

    void BeamModuleAssembler::emit_is_nonempty_list(const ArgVal &Fail, const ArgVal &Src) {
    │ │ │ -  a.test(getArgRef(Src), imm(_TAG_PRIMARY_MASK - TAG_PRIMARY_LIST));
    │ │ │ -  a.jne(labels[Fail.getLabel()]);
    │ │ │ -}

    asmjit provides a fairly straightforward │ │ │ +jit/$ARCH/instr_*.cpp files. For example:

    void BeamModuleAssembler::emit_is_nonempty_list(const ArgVal &Fail, const ArgVal &Src) {
    │ │ │ +  a.test(getArgRef(Src), imm(_TAG_PRIMARY_MASK - TAG_PRIMARY_LIST));
    │ │ │ +  a.jne(labels[Fail.getLabel()]);
    │ │ │ +}

    asmjit provides a fairly straightforward │ │ │ mapping from a C++ function call to the x86 assembly instruction. The above │ │ │ instruction tests if the value in the Src register is a non-empty list and if │ │ │ it is not then it jumps to the fail label.

    For comparison, the interpreter has 8 combinations and specializations of │ │ │ this implementation to minimize the instruction dispatch overhead for │ │ │ common patterns.

    The original register allocation done by the Erlang compiler is used to manage the │ │ │ liveness of values and the physical registers are statically allocated to keep │ │ │ the necessary process state. At the moment this is the static register │ │ │ -allocation on x86-64:

    rbx: ErtsSchedulerRegisters struct (contains x/float registers and some metadata)
    │ │ │ +allocation on x86-64:

    rbx: ErtsSchedulerRegisters struct (contains x/float registers and some metadata)
    │ │ │  rbp: Current frame pointer when `perf` support is enabled, otherwise this
    │ │ │       is an optional save slot for the Erlang stack pointer when executing C
    │ │ │       code.
    │ │ │  r12: Active code index
    │ │ │  r13: Current running process
    │ │ │  r14: Remaining reductions
    │ │ │  r15: Erlang heap pointer

    Note that all of these are callee save registers under the System V and Windows │ │ │ @@ -183,21 +183,21 @@ │ │ │ shared and only the arguments to the instructions vary. Using as little memory as │ │ │ possible has many advantages; less memory is used, loading time decreases, │ │ │ higher cache hit-rate.

    In BeamAsm we need to achieve something similar since the load-time of a module │ │ │ scales almost linearly with the amount of memory it uses. Early BeamAsm prototypes │ │ │ used about double the amount of memory for code as the interpreter, while current │ │ │ versions use about 10% more. How was this achieved?

    In BeamAsm we heavily use shared code fragments to try to emit as much code as │ │ │ possible as global shared fragments instead of duplicating the code unnecessarily. │ │ │ -For instance, the return instruction looks something like this:

    Label yield = a.newLabel();
    │ │ │ +For instance, the return instruction looks something like this:

    Label yield = a.newLabel();
    │ │ │  
    │ │ │  /* Decrement reduction counter */
    │ │ │ -a.dec(FCALLS);
    │ │ │ +a.dec(FCALLS);
    │ │ │  /* If FCALLS < 0, jump to the yield-on-return fragment */
    │ │ │ -a.jl(resolve_fragment(ga->get_dispatch_return()));
    │ │ │ -a.ret();

    The code above is not exactly what is emitted, but close enough. The thing to note │ │ │ +a.jl(resolve_fragment(ga->get_dispatch_return())); │ │ │ +a.ret();

    The code above is not exactly what is emitted, but close enough. The thing to note │ │ │ is that the code for doing the context switch is never emitted. Instead, we jump │ │ │ to a global fragment that all return instructions share. This greatly reduces │ │ │ the amount of code that has to be emitted for each module.

    │ │ │ │ │ │ │ │ │ │ │ │ Running Erlang code │ │ │ @@ -239,43 +239,43 @@ │ │ │ │ │ │ │ │ │ │ │ │ Running C code │ │ │

    │ │ │

    As Erlang stacks can be very small, we have to switch over to a different stack │ │ │ when we need to execute C code (which may expect a much larger stack). This is │ │ │ -done through emit_enter_runtime and emit_leave_runtime, for example:

    mov_arg(ARG4, NumFree);
    │ │ │ +done through emit_enter_runtime and emit_leave_runtime, for example:

    mov_arg(ARG4, NumFree);
    │ │ │  
    │ │ │  /* Move to the C stack and swap out our current reductions, stack-, and
    │ │ │   * heap pointer to the process structure. */
    │ │ │ -emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
    │ │ │ +emit_enter_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
    │ │ │  
    │ │ │ -a.mov(ARG1, c_p);
    │ │ │ -load_x_reg_array(ARG2);
    │ │ │ -make_move_patch(ARG3, lambdas[Fun.getValue()].patches);
    │ │ │ +a.mov(ARG1, c_p);
    │ │ │ +load_x_reg_array(ARG2);
    │ │ │ +make_move_patch(ARG3, lambdas[Fun.getValue()].patches);
    │ │ │  
    │ │ │  /* Call `new_fun`, asserting that we're on the C stack. */
    │ │ │ -runtime_call<4>(new_fun);
    │ │ │ +runtime_call<4>(new_fun);
    │ │ │  
    │ │ │  /* Move back to the C stack, and read the updated values from the process
    │ │ │   * structure */
    │ │ │ -emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
    │ │ │ +emit_leave_runtime<Update::eReductions | Update::eStack | Update::eHeap>();
    │ │ │  
    │ │ │ -a.mov(getXRef(0), RET);

    All combinations of the Update constants are legal, but the ones given to │ │ │ +a.mov(getXRef(0), RET);

    All combinations of the Update constants are legal, but the ones given to │ │ │ emit_leave_runtime must be the same as those given to emit_enter_runtime.

    │ │ │ │ │ │ │ │ │ │ │ │ Tracing and NIF Loading │ │ │

    │ │ │

    To make tracing and NIF loading work there needs to be a way to intercept │ │ │ any function call. In the interpreter, this is done by rewriting the loaded │ │ │ BEAM code, but this is more complicated in BeamAsm as we want to have a fast │ │ │ and compact way to do this. This is solved by emitting the code below at the │ │ │ -start of each function (x86 variant below):

      0x0: short jmp 6 (address 0x8)
    │ │ │ +start of each function (x86 variant below):

      0x0: short jmp 6 (address 0x8)
    │ │ │    0x2: nop
    │ │ │    0x3: relative near call to shared breakpoint fragment
    │ │ │    0x8: actual code for function

    When code starts to execute it will simply see the short jmp 6 instruction │ │ │ which skips the prologue and starts to execute the code directly.

    When we want to enable a certain breakpoint we set the jmp target to be 1, │ │ │ which means it will land on the call to the shared breakpoint fragment. This │ │ │ fragment checks the current breakpoint_flag stored in the ErtsCodeInfo of │ │ │ this function, and then calls erts_call_nif_early and │ │ │ @@ -289,31 +289,31 @@ │ │ │ Updating code │ │ │ │ │ │

    Because many environments enforce W^X it's not always possible to write │ │ │ directly to the code pages. Because of this we map code twice: once with an │ │ │ executable page and once with a writable page. Since they're backed by the │ │ │ same memory, writes to the writable page appear magically in the executable │ │ │ one.

    The erts_writable_code_ptr function can be used to get writable pointers │ │ │ -given a module instance, provided that it has been unsealed first:

    for (i = 0; i < n; i++) {
    │ │ │ +given a module instance, provided that it has been unsealed first:

    for (i = 0; i < n; i++) {
    │ │ │      const ErtsCodeInfo* ci_exec;
    │ │ │      ErtsCodeInfo* ci_rw;
    │ │ │      void *w_ptr;
    │ │ │  
    │ │ │ -    erts_unseal_module(&modp->curr);
    │ │ │ +    erts_unseal_module(&modp->curr);
    │ │ │  
    │ │ │ -    ci_exec = code_hdr->functions[i];
    │ │ │ -    w_ptr = erts_writable_code_ptr(&modp->curr, ci_exec);
    │ │ │ -    ci_rw = (ErtsCodeInfo*)w_ptr;
    │ │ │ +    ci_exec = code_hdr->functions[i];
    │ │ │ +    w_ptr = erts_writable_code_ptr(&modp->curr, ci_exec);
    │ │ │ +    ci_rw = (ErtsCodeInfo*)w_ptr;
    │ │ │  
    │ │ │ -    uninstall_breakpoint(ci_rw, ci_exec);
    │ │ │ -    consolidate_bp_data(modp, ci_rw, 1);
    │ │ │ -    ASSERT(ci_rw->gen_bp == NULL);
    │ │ │ +    uninstall_breakpoint(ci_rw, ci_exec);
    │ │ │ +    consolidate_bp_data(modp, ci_rw, 1);
    │ │ │ +    ASSERT(ci_rw->gen_bp == NULL);
    │ │ │  
    │ │ │ -    erts_seal_module(&modp->curr);
    │ │ │ -}

    Without the module instance there's no reliable way to figure out the writable │ │ │ + erts_seal_module(&modp->curr); │ │ │ +}

    Without the module instance there's no reliable way to figure out the writable │ │ │ address of a code page, and we rely on address space layout randomization │ │ │ (ASLR) to make it difficult to guess. On some platforms, security is further │ │ │ enhanced by protecting the writable area from writes until the module has been │ │ │ unsealed by erts_unseal_module.

    │ │ │ │ │ │ │ │ │ │ │ │ @@ -393,15 +393,15 @@ │ │ │ perf script > out.perf │ │ │ ## run stackcollapse │ │ │ stackcollapse-perf.pl out.perf > out.folded │ │ │ ## Create the svg │ │ │ flamegraph.pl out.folded > out.svg

    We get a graph that would look something like this:

    Linux Perf FlameGraph: dialyzer PLT build

    You can view a larger version here. It contains │ │ │ the same information, but it is easier to share with others as it does │ │ │ not need the symbols in the executable.

    Using the same data we can also produce a graph where the scheduler profile data │ │ │ -has been merged by using sed:

    ## Strip [0-9]+_ and/or _[0-9]+ from all scheduler names
    │ │ │ +has been merged by using sed:

    ## Strip [0-9]+_ and/or _[0-9]+ from all scheduler names
    │ │ │  ## scheduler names changed in OTP26, hence two expressions
    │ │ │  sed -e 's/^[0-9]\+_//' -e 's/^erts_\([^_]\+\)_[0-9]\+/erts_\1/' out.folded > out.folded_sched
    │ │ │  ## Create the svg
    │ │ │  flamegraph.pl out.folded_sched > out_sched.svg

    Linux Perf FlameGraph: dialyzer PLT build

    You can view a larger version here. │ │ │ There are many different transformations that you can do to make the graph show │ │ │ you what you want.

    │ │ │ │ │ ├── ./usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/codeloading.html │ │ │ @@ -163,16 +163,16 @@ │ │ │ only be done by one loader process at a time. A second loader process │ │ │ trying to enter finishing phase will be suspended until the first │ │ │ loader is done. This will only block the process, the scheduler is │ │ │ free to schedule other work while the second loader is waiting. (See │ │ │ erts_try_seize_code_load_permission and │ │ │ erts_release_code_load_permission).

    The ability to prepare several modules in parallel is not currently │ │ │ used as almost all code loading is serialized by the code_server │ │ │ -process. The BIF interface is however prepared for this.

      erlang:prepare_loading(Module, Code) -> LoaderState
    │ │ │ -  erlang:finish_loading([LoaderState])

    The idea is that prepare_loading could be called in parallel for │ │ │ +process. The BIF interface is however prepared for this.

      erlang:prepare_loading(Module, Code) -> LoaderState
    │ │ │ +  erlang:finish_loading([LoaderState])

    The idea is that prepare_loading could be called in parallel for │ │ │ different modules and returns a "magic binary" containing the internal │ │ │ state of each prepared module. Function finish_loading could take a │ │ │ list of such states and do the finishing of all of them in one go.

    Currently we use the legacy BIF erlang:load_module which is now │ │ │ implemented in Erlang by calling the above two functions in │ │ │ sequence. Function finish_loading is limited to only accepts a list │ │ │ with one module state as we do not yet use the multi module loading │ │ │ feature.

    │ │ ├── ./usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/crash_dump.html │ │ │ @@ -401,21 +401,21 @@ │ │ │ put/2 and get/1 thing) is non-empty.

    The raw memory information can be decoded by the Crashdump Viewer tool. You can │ │ │ then see the stack dump, the message queue (if any), and the dictionary (if │ │ │ any).

    The stack dump is a dump of the Erlang process stack. Most of the live data │ │ │ (that is, variables currently in use) are placed on the stack; thus this can be │ │ │ interesting. One has to "guess" what is what, but as the information is │ │ │ symbolic, thorough reading of this information can be useful. As an example, we │ │ │ can find the state variable of the Erlang primitive loader online (5) and │ │ │ -(6) in the following example:

    (1)  3cac44   Return addr 0x13BF58 (<terminate process normally>)
    │ │ │ -(2)  y(0)     ["/view/siri_r10_dev/clearcase/otp/erts/lib/kernel/ebin",
    │ │ │ -(3)            "/view/siri_r10_dev/clearcase/otp/erts/lib/stdlib/ebin"]
    │ │ │ -(4)  y(1)     <0.1.0>
    │ │ │ -(5)  y(2)     {state,[],none,#Fun<erl_prim_loader.6.7085890>,undefined,#Fun<erl_prim_loader.7.9000327>,
    │ │ │ -(6)            #Fun<erl_prim_loader.8.116480692>,#Port<0.2>,infinity,#Fun<erl_prim_loader.9.10708760>}
    │ │ │ -(7)  y(3)     infinity

    When interpreting the data for a process, it is helpful to know that anonymous │ │ │ +(6) in the following example:

    (1)  3cac44   Return addr 0x13BF58 (<terminate process normally>)
    │ │ │ +(2)  y(0)     ["/view/siri_r10_dev/clearcase/otp/erts/lib/kernel/ebin",
    │ │ │ +(3)            "/view/siri_r10_dev/clearcase/otp/erts/lib/stdlib/ebin"]
    │ │ │ +(4)  y(1)     <0.1.0>
    │ │ │ +(5)  y(2)     {state,[],none,#Fun<erl_prim_loader.6.7085890>,undefined,#Fun<erl_prim_loader.7.9000327>,
    │ │ │ +(6)            #Fun<erl_prim_loader.8.116480692>,#Port<0.2>,infinity,#Fun<erl_prim_loader.9.10708760>}
    │ │ │ +(7)  y(3)     infinity

    When interpreting the data for a process, it is helpful to know that anonymous │ │ │ function objects (funs) are given the following:

    • A name constructed from the name of the function in which they are created
    • A number (starting with 0) indicating the number of that fun within that │ │ │ function

    │ │ │ │ │ │ │ │ │ │ │ │ Atoms │ │ │

    │ │ ├── ./usr/share/doc/erlang-doc/html/erts-15.2.7.8/doc/html/driver.html │ │ │ @@ -176,20 +176,20 @@ │ │ │ simple set of commands: connect to log in to the database, disconnect to log │ │ │ out, and select to send a SQL-query and get the result. All results are │ │ │ returned through rbuf. The library ei in erl_interface │ │ │ is used to encode data in binary term format. The result is returned to the emulator as binary │ │ │ terms, so binary_to_term is called in Erlang to convert the result to term │ │ │ form.

    The code is available in pg_sync.c in the sample directory of erts.

    The driver entry contains the functions that will be called by the emulator. In │ │ │ this example, only start, stop, and control are provided:

    /* Driver interface declarations */
    │ │ │ -static ErlDrvData start(ErlDrvPort port, char *command);
    │ │ │ -static void stop(ErlDrvData drv_data);
    │ │ │ -static int control(ErlDrvData drv_data, unsigned int command, char *buf,
    │ │ │ -                   int len, char **rbuf, int rlen);
    │ │ │ +static ErlDrvData start(ErlDrvPort port, char *command);
    │ │ │ +static void stop(ErlDrvData drv_data);
    │ │ │ +static int control(ErlDrvData drv_data, unsigned int command, char *buf,
    │ │ │ +                   int len, char **rbuf, int rlen);
    │ │ │  
    │ │ │ -static ErlDrvEntry pq_driver_entry = {
    │ │ │ +static ErlDrvEntry pq_driver_entry = {
    │ │ │      NULL,                        /* init */
    │ │ │      start,
    │ │ │      stop,
    │ │ │      NULL,                        /* output */
    │ │ │      NULL,                        /* ready_input */
    │ │ │      NULL,                        /* ready_output */
    │ │ │      "pg_sync",                   /* the name of the driver */
    │ │ │ @@ -198,18 +198,18 @@
    │ │ │      control,
    │ │ │      NULL,                        /* timeout */
    │ │ │      NULL,                        /* outputv */
    │ │ │      NULL,                        /* ready_async */
    │ │ │      NULL,                        /* flush */
    │ │ │      NULL,                        /* call */
    │ │ │      NULL                         /* event */
    │ │ │ -};

    We have a structure to store state needed by the driver, in this case we only │ │ │ -need to keep the database connection:

    typedef struct our_data_s {
    │ │ │ +};

    We have a structure to store state needed by the driver, in this case we only │ │ │ +need to keep the database connection:

    typedef struct our_data_s {
    │ │ │      PGconn* conn;
    │ │ │ -} our_data_t;

    The control codes that we have defined are as follows:

    /* Keep the following definitions in alignment with the
    │ │ │ +} our_data_t;

    The control codes that we have defined are as follows:

    /* Keep the following definitions in alignment with the
    │ │ │   * defines in erl_pq_sync.erl
    │ │ │   */
    │ │ │  
    │ │ │  #define DRV_CONNECT             'C'
    │ │ │  #define DRV_DISCONNECT          'D'
    │ │ │  #define DRV_SELECT              'S'

    This returns the driver structure. The macro DRIVER_INIT defines the only │ │ │ exported function. All the other functions are static, and will not be exported │ │ │ @@ -217,138 +217,138 @@ │ │ │ │ │ │ /* │ │ │ * This is the init function called after this driver has been loaded. │ │ │ * It must *not* be declared static. Must return the address to │ │ │ * the driver entry. │ │ │ */ │ │ │ │ │ │ -DRIVER_INIT(pq_drv) │ │ │ -{ │ │ │ +DRIVER_INIT(pq_drv) │ │ │ +{ │ │ │ return &pq_driver_entry; │ │ │ -}

    Here some initialization is done, start is called from open_port/2. The data │ │ │ +}

    Here some initialization is done, start is called from open_port/2. The data │ │ │ will be passed to control and stop.

    /* DRIVER INTERFACE */
    │ │ │ -static ErlDrvData start(ErlDrvPort port, char *command)
    │ │ │ -{
    │ │ │ +static ErlDrvData start(ErlDrvPort port, char *command)
    │ │ │ +{
    │ │ │      our_data_t* data;
    │ │ │  
    │ │ │ -    data = (our_data_t*)driver_alloc(sizeof(our_data_t));
    │ │ │ +    data = (our_data_t*)driver_alloc(sizeof(our_data_t));
    │ │ │      data->conn = NULL;
    │ │ │ -    set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY);
    │ │ │ -    return (ErlDrvData)data;
    │ │ │ -}

    We call disconnect to log out from the database. (This should have been done │ │ │ -from Erlang, but just in case.)

    static int do_disconnect(our_data_t* data, ei_x_buff* x);
    │ │ │ -
    │ │ │ -static void stop(ErlDrvData drv_data)
    │ │ │ -{
    │ │ │ -    our_data_t* data = (our_data_t*)drv_data;
    │ │ │ -
    │ │ │ -    do_disconnect(data, NULL);
    │ │ │ -    driver_free(data);
    │ │ │ -}

    We use the binary format only to return data to the emulator; input data is a │ │ │ + set_port_control_flags(port, PORT_CONTROL_FLAG_BINARY); │ │ │ + return (ErlDrvData)data; │ │ │ +}

    We call disconnect to log out from the database. (This should have been done │ │ │ +from Erlang, but just in case.)

    static int do_disconnect(our_data_t* data, ei_x_buff* x);
    │ │ │ +
    │ │ │ +static void stop(ErlDrvData drv_data)
    │ │ │ +{
    │ │ │ +    our_data_t* data = (our_data_t*)drv_data;
    │ │ │ +
    │ │ │ +    do_disconnect(data, NULL);
    │ │ │ +    driver_free(data);
    │ │ │ +}

    We use the binary format only to return data to the emulator; input data is a │ │ │ string parameter for connect and select. The returned data consists of │ │ │ Erlang terms.

    The functions get_s and ei_x_to_new_binary are utilities that are used to │ │ │ make the code shorter. get_s duplicates the string and zero-terminates it, as │ │ │ the postgres client library wants that. ei_x_to_new_binary takes an │ │ │ ei_x_buff buffer, allocates a binary, and copies the data there. This binary │ │ │ is returned in *rbuf. (Notice that this binary is freed by the emulator, not │ │ │ -by us.)

    static char* get_s(const char* buf, int len);
    │ │ │ -static int do_connect(const char *s, our_data_t* data, ei_x_buff* x);
    │ │ │ -static int do_select(const char* s, our_data_t* data, ei_x_buff* x);
    │ │ │ +by us.)

    static char* get_s(const char* buf, int len);
    │ │ │ +static int do_connect(const char *s, our_data_t* data, ei_x_buff* x);
    │ │ │ +static int do_select(const char* s, our_data_t* data, ei_x_buff* x);
    │ │ │  
    │ │ │  /* As we are operating in binary mode, the return value from control
    │ │ │   * is irrelevant, as long as it is not negative.
    │ │ │   */
    │ │ │ -static int control(ErlDrvData drv_data, unsigned int command, char *buf,
    │ │ │ -                   int len, char **rbuf, int rlen)
    │ │ │ -{
    │ │ │ +static int control(ErlDrvData drv_data, unsigned int command, char *buf,
    │ │ │ +                   int len, char **rbuf, int rlen)
    │ │ │ +{
    │ │ │      int r;
    │ │ │      ei_x_buff x;
    │ │ │ -    our_data_t* data = (our_data_t*)drv_data;
    │ │ │ -    char* s = get_s(buf, len);
    │ │ │ -    ei_x_new_with_version(&x);
    │ │ │ -    switch (command) {
    │ │ │ -        case DRV_CONNECT:    r = do_connect(s, data, &x);  break;
    │ │ │ -        case DRV_DISCONNECT: r = do_disconnect(data, &x);  break;
    │ │ │ -        case DRV_SELECT:     r = do_select(s, data, &x);   break;
    │ │ │ +    our_data_t* data = (our_data_t*)drv_data;
    │ │ │ +    char* s = get_s(buf, len);
    │ │ │ +    ei_x_new_with_version(&x);
    │ │ │ +    switch (command) {
    │ │ │ +        case DRV_CONNECT:    r = do_connect(s, data, &x);  break;
    │ │ │ +        case DRV_DISCONNECT: r = do_disconnect(data, &x);  break;
    │ │ │ +        case DRV_SELECT:     r = do_select(s, data, &x);   break;
    │ │ │          default:             r = -1;        break;
    │ │ │ -    }
    │ │ │ -    *rbuf = (char*)ei_x_to_new_binary(&x);
    │ │ │ -    ei_x_free(&x);
    │ │ │ -    driver_free(s);
    │ │ │ +    }
    │ │ │ +    *rbuf = (char*)ei_x_to_new_binary(&x);
    │ │ │ +    ei_x_free(&x);
    │ │ │ +    driver_free(s);
    │ │ │      return r;
    │ │ │ -}

    do_connect is where we log in to the database. If the connection was │ │ │ +}

    do_connect is where we log in to the database. If the connection was │ │ │ successful, we store the connection handle in the driver data, and return │ │ │ 'ok'. Otherwise, we return the error message from postgres and store NULL in │ │ │ -the driver data.

    static int do_connect(const char *s, our_data_t* data, ei_x_buff* x)
    │ │ │ -{
    │ │ │ -    PGconn* conn = PQconnectdb(s);
    │ │ │ -    if (PQstatus(conn) != CONNECTION_OK) {
    │ │ │ -        encode_error(x, conn);
    │ │ │ -        PQfinish(conn);
    │ │ │ +the driver data.

    static int do_connect(const char *s, our_data_t* data, ei_x_buff* x)
    │ │ │ +{
    │ │ │ +    PGconn* conn = PQconnectdb(s);
    │ │ │ +    if (PQstatus(conn) != CONNECTION_OK) {
    │ │ │ +        encode_error(x, conn);
    │ │ │ +        PQfinish(conn);
    │ │ │          conn = NULL;
    │ │ │ -    } else {
    │ │ │ -        encode_ok(x);
    │ │ │ -    }
    │ │ │ +    } else {
    │ │ │ +        encode_ok(x);
    │ │ │ +    }
    │ │ │      data->conn = conn;
    │ │ │      return 0;
    │ │ │ -}

    If we are connected (and if the connection handle is not NULL), we log out │ │ │ +}

    If we are connected (and if the connection handle is not NULL), we log out │ │ │ from the database. We need to check if we should encode an 'ok', as we can get │ │ │ -here from function stop, which does not return data to the emulator:

    static int do_disconnect(our_data_t* data, ei_x_buff* x)
    │ │ │ -{
    │ │ │ -    if (data->conn == NULL)
    │ │ │ +here from function stop, which does not return data to the emulator:

    static int do_disconnect(our_data_t* data, ei_x_buff* x)
    │ │ │ +{
    │ │ │ +    if (data->conn == NULL)
    │ │ │          return 0;
    │ │ │ -    PQfinish(data->conn);
    │ │ │ +    PQfinish(data->conn);
    │ │ │      data->conn = NULL;
    │ │ │ -    if (x != NULL)
    │ │ │ -        encode_ok(x);
    │ │ │ +    if (x != NULL)
    │ │ │ +        encode_ok(x);
    │ │ │      return 0;
    │ │ │ -}

    We execute a query and encode the result. Encoding is done in another C module, │ │ │ -pg_encode.c, which is also provided as sample code.

    static int do_select(const char* s, our_data_t* data, ei_x_buff* x)
    │ │ │ -{
    │ │ │ -   PGresult* res = PQexec(data->conn, s);
    │ │ │ -    encode_result(x, res, data->conn);
    │ │ │ -    PQclear(res);
    │ │ │ +}

    We execute a query and encode the result. Encoding is done in another C module, │ │ │ +pg_encode.c, which is also provided as sample code.

    static int do_select(const char* s, our_data_t* data, ei_x_buff* x)
    │ │ │ +{
    │ │ │ +   PGresult* res = PQexec(data->conn, s);
    │ │ │ +    encode_result(x, res, data->conn);
    │ │ │ +    PQclear(res);
    │ │ │      return 0;
    │ │ │ -}

    Here we check the result from postgres. If it is data, we encode it as lists of │ │ │ +}

    Here we check the result from postgres. If it is data, we encode it as lists of │ │ │ lists with column data. Everything from postgres is C strings, so we use │ │ │ ei_x_encode_string to send the result as strings to Erlang. (The head of the │ │ │ -list contains the column names.)

    void encode_result(ei_x_buff* x, PGresult* res, PGconn* conn)
    │ │ │ -{
    │ │ │ +list contains the column names.)

    void encode_result(ei_x_buff* x, PGresult* res, PGconn* conn)
    │ │ │ +{
    │ │ │      int row, n_rows, col, n_cols;
    │ │ │ -    switch (PQresultStatus(res)) {
    │ │ │ +    switch (PQresultStatus(res)) {
    │ │ │      case PGRES_TUPLES_OK:
    │ │ │ -        n_rows = PQntuples(res);
    │ │ │ -        n_cols = PQnfields(res);
    │ │ │ -        ei_x_encode_tuple_header(x, 2);
    │ │ │ -        encode_ok(x);
    │ │ │ -        ei_x_encode_list_header(x, n_rows+1);
    │ │ │ -        ei_x_encode_list_header(x, n_cols);
    │ │ │ -        for (col = 0; col < n_cols; ++col) {
    │ │ │ -            ei_x_encode_string(x, PQfname(res, col));
    │ │ │ -        }
    │ │ │ -        ei_x_encode_empty_list(x);
    │ │ │ -        for (row = 0; row < n_rows; ++row) {
    │ │ │ -            ei_x_encode_list_header(x, n_cols);
    │ │ │ -            for (col = 0;