--- /srv/rebuilderd/tmp/rebuilderdFRCUV2/inputs/erlang-doc_27.3.4.11+dfsg-4_all.deb +++ /srv/rebuilderd/tmp/rebuilderdFRCUV2/out/erlang-doc_27.3.4.11+dfsg-4_all.deb ├── file list │ @@ -1,3 +1,3 @@ │ -rw-r--r-- 0 0 0 4 2026-05-17 12:22:41.000000 debian-binary │ --rw-r--r-- 0 0 0 40652 2026-05-17 12:22:41.000000 control.tar.xz │ --rw-r--r-- 0 0 0 21045924 2026-05-17 12:22:41.000000 data.tar.xz │ +-rw-r--r-- 0 0 0 40628 2026-05-17 12:22:41.000000 control.tar.xz │ +-rw-r--r-- 0 0 0 21045264 2026-05-17 12:22:41.000000 data.tar.xz ├── control.tar.xz │ ├── control.tar │ │ ├── ./md5sums │ │ │ ├── ./md5sums │ │ │ │┄ Files differ │ │ │ ├── line order │ │ │ │ @@ -317,15 +317,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/dist/search_data-656BDBDC.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/dist/search_data-382BFC7C.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/dist/sidebar_items-9A100A55.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/notes.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/search.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/.build │ │ │ │ usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/404.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/api-reference.html │ │ │ │ @@ -370,15 +370,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/dist/search_data-47E11C0C.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/dist/search_data-7ECECF0A.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/dist/sidebar_items-020EA270.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/event_handler_chapter.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/example_chapter.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/getting_started_chapter.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/install_chapter.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/introduction.html │ │ │ │ @@ -516,15 +516,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/search_data-0488DFD4.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/search_data-6D666814.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/sidebar_items-F976ACE0.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/notes.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/search.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/typer_cmd.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/.build │ │ │ │ usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/404.html │ │ │ │ @@ -1073,15 +1073,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/dist/search_data-B0144FAD.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/dist/search_data-7DE65072.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/dist/sidebar_items-017D32EE.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/megaco.epub │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/megaco.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/megaco_architecture.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/megaco_codec_meas.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/megaco_codec_mstone1.html │ │ │ │ @@ -1123,15 +1123,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/dist/search_data-4C102A02.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/dist/search_data-BC67FB4D.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/dist/sidebar_items-A19B2882.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/mnesia.epub │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/mnesia.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/mnesia_app_a.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/mnesia_app_b.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/mnesia_app_c.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-FFC02264.js │ │ │ │ +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/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 │ │ │ │ @@ -1206,15 +1206,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/search_data-465DEDF7.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/search_data-D78563F6.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/sidebar_items-19ECDBA9.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/error_handling.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/getting_started.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/introduction.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/notes.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/odbc.epub │ │ │ │ @@ -1446,15 +1446,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/search_data-D388DBD3.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/search_data-18B48D27.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/sidebar_items-E4326166.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/index.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/notes.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/search.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/snmp.epub │ │ │ │ usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/snmp.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/snmp_advanced_agent.html │ │ │ │ @@ -1620,15 +1620,15 @@ │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ │ -usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/dist/search_data-0C02F667.js │ │ │ │ +usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/dist/search_data-BB0E13B7.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/dist/sidebar_items-79C80363.js │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/edlin.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/edlin_expand.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/epp.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/erl_anno.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/erl_error.html │ │ │ │ usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/erl_eval.html ├── data.tar.xz │ ├── data.tar │ │ ├── file list │ │ │ @@ -137,15 +137,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 293 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/doc/ssh.html │ │ │ -rw-r--r-- 0 root (0) root (0) 294 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/doc/ssl.html │ │ │ -rw-r--r-- 0 root (0) root (0) 295 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/doc/stdlib.html │ │ │ -rw-r--r-- 0 root (0) root (0) 301 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/doc/syntax_tools.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/doc/system/ │ │ │ -rw-r--r-- 0 root (0) root (0) 2286 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/doc/system/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 5651 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/doc/system/404.html │ │ │ --rw-r--r-- 0 root (0) root (0) 660443 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/doc/system/Erlang System Documentation.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 660451 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/doc/system/Erlang System Documentation.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 53697 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/doc/system/applications.html │ │ │ -rw-r--r-- 0 root (0) root (0) 109782 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/doc/system/appup_cookbook.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 7982 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/ballpoint-pen.svg │ │ │ -rw-r--r-- 0 root (0) root (0) 2284 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/dist1.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 5214 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/dist2.gif │ │ │ -rw-r--r-- 0 root (0) root (0) 5007 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/doc/system/assets/dist3.gif │ │ │ @@ -351,15 +351,15 @@ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 96636 2026-05-17 12:22:41.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) 96630 2026-05-17 12:22:41.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) 143119 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/assets/exclusive_Win_But.gif │ │ │ @@ -380,15 +380,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5820 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5612 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5552 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 150048 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/dist/search_data-656BDBDC.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 150048 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/dist/search_data-382BFC7C.js │ │ │ -rw-r--r-- 0 root (0) root (0) 5693 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/dist/sidebar_items-9A100A55.js │ │ │ -rw-r--r-- 0 root (0) root (0) 264 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 98943 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5926 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/asn1-5.3.4.2/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/ │ │ │ @@ -397,15 +397,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 10672 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 21772 2026-05-17 12:22:41.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) 399333 2026-05-17 12:22:41.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) 399353 2026-05-17 12:22:41.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-17 12:22:41.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) 59379 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/ct_hooks.html │ │ │ @@ -438,15 +438,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5820 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5612 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5552 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 585051 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/dist/search_data-47E11C0C.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 585051 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/dist/search_data-7ECECF0A.js │ │ │ -rw-r--r-- 0 root (0) root (0) 58564 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/dist/sidebar_items-020EA270.js │ │ │ -rw-r--r-- 0 root (0) root (0) 31486 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/event_handler_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 62180 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/example_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 26648 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/getting_started_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 270 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 8025 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/install_chapter.html │ │ │ -rw-r--r-- 0 root (0) root (0) 8890 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/common_test-1.27.7/doc/html/introduction.html │ │ │ @@ -466,15 +466,15 @@ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 182717 2026-05-17 12:22:41.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) 182716 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/compiler-8.6.1.4/doc/html/dist/inconsolata-latin-700-normal-YRJBXG74.woff2 │ │ │ @@ -500,15 +500,15 @@ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3.2/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 992 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3.2/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6022 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3.2/doc/html/404.html │ │ │ -rw-r--r-- 0 root (0) root (0) 35145 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3.2/doc/html/algorithm_details.html │ │ │ -rw-r--r-- 0 root (0) root (0) 6678 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3.2/doc/html/api-reference.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3.2/doc/html/assets/ │ │ │ -rw-r--r-- 0 root (0) root (0) 5837 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3.2/doc/html/assets/logo.png │ │ │ --rw-r--r-- 0 root (0) root (0) 127367 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3.2/doc/html/crypto.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 127364 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3.2/doc/html/crypto.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 294879 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3.2/doc/html/crypto.html │ │ │ -rw-r--r-- 0 root (0) root (0) 10024 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3.2/doc/html/crypto_app.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3.2/doc/html/dist/ │ │ │ -rw-r--r-- 0 root (0) root (0) 19268 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3.2/doc/html/dist/handlebars.runtime-WZY45QD2.js │ │ │ -rw-r--r-- 0 root (0) root (0) 33556 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3.2/doc/html/dist/handlebars.templates-W6ARKZMU.js │ │ │ -rw-r--r-- 0 root (0) root (0) 70576 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3.2/doc/html/dist/html-RFEC7MEN.js │ │ │ -rw-r--r-- 0 root (0) root (0) 67452 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/crypto-5.5.3.2/doc/html/dist/html-erlang-V4OM2JZP.css │ │ │ @@ -583,15 +583,15 @@ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 66387 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dialyzer.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 66393 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/html-erlang-V4OM2JZP.css │ │ │ @@ -604,29 +604,29 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5820 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5612 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5552 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 122270 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/search_data-0488DFD4.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 122270 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/search_data-6D666814.js │ │ │ -rw-r--r-- 0 root (0) root (0) 7336 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/dist/sidebar_items-F976ACE0.js │ │ │ -rw-r--r-- 0 root (0) root (0) 266 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 132207 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5944 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/search.html │ │ │ -rw-r--r-- 0 root (0) root (0) 10876 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/dialyzer-5.3.1/doc/html/typer_cmd.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 144806 2026-05-17 12:22:41.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) 144805 2026-05-17 12:22:41.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) 256968 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/diameter-2.4.1.1/doc/html/diameter_make.html │ │ │ @@ -752,15 +752,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 33169 2026-05-17 12:22:41.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) 33173 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/ │ │ │ @@ -789,15 +789,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 193099 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/erl_interface-5.5.2/doc/html/dist/search_data-1F14090C.js │ │ │ -rw-r--r-- 0 root (0) root (0) 15936 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 99316 2026-05-17 12:22:41.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) 99320 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 302464 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/et.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 302458 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 52825 2026-05-17 12:22:41.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) 100508 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/et-1.7.1/doc/html/et_tutorial.html │ │ │ @@ -907,15 +907,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 33169 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/ftp.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 33168 2026-05-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/ftp-1.2.3/doc/html/ftp.html │ │ │ -rw-r--r-- 0 root (0) root (0) 12526 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 121919 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 152918 2026-05-17 12:22:41.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) 152921 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 20005 2026-05-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/inets-9.3.2.4/doc/html/mod_security.html │ │ │ @@ -1169,15 +1169,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 57284 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 793563 2026-05-17 12:22:41.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) 793538 2026-05-17 12:22:41.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-17 12:22:41.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) 188603 2026-05-17 12:22:41.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) 108823 2026-05-17 12:22:41.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) 70795 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/kernel-10.2.7.4/doc/html/logger_formatter.html │ │ │ @@ -1226,18 +1226,18 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5820 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/dist/search_data-B0144FAD.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 200508 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 182312 2026-05-17 12:22:41.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) 182319 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 18616 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/megaco-4.7.2.1/doc/html/megaco_debug.html │ │ │ @@ -1281,18 +1281,18 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5820 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 376330 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/mnesia-4.23.5.2/doc/html/dist/search_data-BC67FB4D.js │ │ │ -rw-r--r-- 0 root (0) root (0) 24634 2026-05-17 12:22:41.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-17 12:22:41.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) 222178 2026-05-17 12:22:41.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) 222191 2026-05-17 12:22:41.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) 320925 2026-05-17 12:22:41.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) 45428 2026-05-17 12:22:41.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) 87244 2026-05-17 12:22:41.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) 46043 2026-05-17 12:22:41.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-17 12:22:41.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) 109257 2026-05-17 12:22:41.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) 51398 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 146200 2026-05-17 12:22:41.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) 12765 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 116839 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/observer.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 116841 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/observer-2.17/doc/html/ttb.html │ │ │ -rw-r--r-- 0 root (0) root (0) 165630 2026-05-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/ │ │ │ @@ -1374,22 +1374,22 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5820 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5612 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5552 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 76343 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/dist/search_data-465DEDF7.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 76343 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/error_handling.html │ │ │ -rw-r--r-- 0 root (0) root (0) 51340 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 67292 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/odbc-2.15/doc/html/odbc.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 67293 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/os_mon-2.10.1/doc/html/dist/search_data-30902A86.js │ │ │ -rw-r--r-- 0 root (0) root (0) 7923 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 50280 2026-05-17 12:22:41.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) 50282 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 890 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 44445 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/parsetools-2.6/doc/html/parsetools.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 44441 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 100369 2026-05-17 12:22:41.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) 100362 2026-05-17 12:22:41.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) 207120 2026-05-17 12:22:41.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-17 12:22:41.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) 70630 2026-05-17 12:22:41.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-17 12:22:41.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) 131385 2026-05-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/ │ │ │ @@ -1522,15 +1522,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5612 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5552 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 90283 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/search_data-8726EDEC.js │ │ │ -rw-r--r-- 0 root (0) root (0) 8840 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/dist/sidebar_items-DF937488.js │ │ │ -rw-r--r-- 0 root (0) root (0) 265 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 46277 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/notes.html │ │ │ --rw-r--r-- 0 root (0) root (0) 63039 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/reltool.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 63037 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/reltool.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 100627 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/reltool.html │ │ │ -rw-r--r-- 0 root (0) root (0) 200810 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/reltool_examples.html │ │ │ -rw-r--r-- 0 root (0) root (0) 9368 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/reltool_intro.html │ │ │ -rw-r--r-- 0 root (0) root (0) 23138 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/reltool_usage.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5938 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/reltool-1.0.1/doc/html/search.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/runtime_tools-2.1.1/doc/ │ │ │ @@ -1564,15 +1564,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 9774 2026-05-17 12:22:41.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) 47867 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 119363 2026-05-17 12:22:41.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) 119353 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/sasl-4.2.2.1/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 92587 2026-05-17 12:22:41.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) 92585 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/ │ │ │ @@ -1649,20 +1649,20 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5820 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5612 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5552 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/dist/search_data-D388DBD3.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 549455 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 445348 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/snmp.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 445343 2026-05-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/snmp-5.18.2/doc/html/snmp.html │ │ │ -rw-r--r-- 0 root (0) root (0) 39937 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 27780 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 274695 2026-05-17 12:22:41.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) 274706 2026-05-17 12:22:41.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) 250293 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 213543 2026-05-17 12:22:41.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) 213549 2026-05-17 12:22:41.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) 322605 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 39368 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/ssl-11.2.12.7/doc/html/ssl_session_cache_api.html │ │ │ @@ -1838,15 +1838,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 23236 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/dist/lato-latin-300-normal-YUMVEFOL.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23580 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/dist/lato-latin-400-normal-W7754I4D.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 23040 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/dist/lato-latin-700-normal-2XVSBPG4.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5820 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/dist/lato-latin-ext-300-normal-DG5RYZUK.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5612 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/dist/lato-latin-ext-400-normal-ZXDJ7C2U.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 5552 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/dist/lato-latin-ext-700-normal-3JFX6WED.woff2 │ │ │ -rw-r--r-- 0 root (0) root (0) 1956 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/dist/remixicon-NKANDIL5.woff2 │ │ │ --rw-r--r-- 0 root (0) root (0) 1846642 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/dist/search_data-0C02F667.js │ │ │ +-rw-r--r-- 0 root (0) root (0) 1846642 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/dist/search_data-BB0E13B7.js │ │ │ -rw-r--r-- 0 root (0) root (0) 241821 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/dist/sidebar_items-79C80363.js │ │ │ -rw-r--r-- 0 root (0) root (0) 18711 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/edlin.html │ │ │ -rw-r--r-- 0 root (0) root (0) 15308 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/edlin_expand.html │ │ │ -rw-r--r-- 0 root (0) root (0) 52741 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/epp.html │ │ │ -rw-r--r-- 0 root (0) root (0) 41182 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/erl_anno.html │ │ │ -rw-r--r-- 0 root (0) root (0) 34109 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/erl_error.html │ │ │ -rw-r--r-- 0 root (0) root (0) 57502 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/erl_eval.html │ │ │ @@ -1896,15 +1896,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 5938 2026-05-17 12:22:41.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-17 12:22:41.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) 106915 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 354796 2026-05-17 12:22:41.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) 1411447 2026-05-17 12:22:41.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) 1411426 2026-05-17 12:22:41.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-17 12:22:41.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) 193521 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 81769 2026-05-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/stdlib-6.2.2.3/doc/html/unicode.html │ │ │ @@ -1984,15 +1984,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 26219 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.2.1/doc/html/dist/search_data-F3ACFEF4.js │ │ │ -rw-r--r-- 0 root (0) root (0) 3089 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.2.1/doc/html/dist/sidebar_items-F16FBB4B.js │ │ │ -rw-r--r-- 0 root (0) root (0) 10505 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.2.1/doc/html/getting_started.html │ │ │ -rw-r--r-- 0 root (0) root (0) 264 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.2.1/doc/html/index.html │ │ │ -rw-r--r-- 0 root (0) root (0) 8818 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.2.1/doc/html/introduction.html │ │ │ -rw-r--r-- 0 root (0) root (0) 16338 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.2.1/doc/html/notes.html │ │ │ -rw-r--r-- 0 root (0) root (0) 5926 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.2.1/doc/html/search.html │ │ │ --rw-r--r-- 0 root (0) root (0) 30495 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.2.1/doc/html/tftp.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 30496 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.2.1/doc/html/tftp.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 45483 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.2.1/doc/html/tftp.html │ │ │ -rw-r--r-- 0 root (0) root (0) 11785 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/tftp-1.2.2.1/doc/html/tftp_logger.html │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/ │ │ │ -rw-r--r-- 0 root (0) root (0) 1139 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/.build │ │ │ -rw-r--r-- 0 root (0) root (0) 6010 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/404.html │ │ │ @@ -2033,15 +2033,15 @@ │ │ │ -rw-r--r-- 0 root (0) root (0) 263 2026-05-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/lcnt.html │ │ │ -rw-r--r-- 0 root (0) root (0) 53459 2026-05-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/make.html │ │ │ -rw-r--r-- 0 root (0) root (0) 107155 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/tags.html │ │ │ --rw-r--r-- 0 root (0) root (0) 239429 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/tools.epub │ │ │ +-rw-r--r-- 0 root (0) root (0) 239417 2026-05-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/tools.epub │ │ │ -rw-r--r-- 0 root (0) root (0) 174025 2026-05-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/tools-4.1.1/doc/html/xref.html │ │ │ -rw-r--r-- 0 root (0) root (0) 39638 2026-05-17 12:22:41.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-17 12:22:41.000000 ./usr/share/doc/erlang-doc/html/lib/wx-2.4.3.1/ │ │ │ drwxr-xr-x 0 root (0) root (0) 0 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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) 1607625 2026-05-17 12:22:41.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) 1607623 2026-05-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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-17 12:22:41.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: 660443 bytes, number of entries: 91 │ │ │ │ -?rw-r--r-- 6.1 unx 20 bx stor 26-May-17 13:22 mimetype │ │ │ │ -?rw-r--r-- 6.1 unx 17923 bx defN 26-May-17 13:22 OEBPS/versions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 4674 bx defN 26-May-17 13:22 OEBPS/upgrade.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 53442 bx defN 26-May-17 13:22 OEBPS/typespec.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2167 bx defN 26-May-17 13:22 OEBPS/tutorial.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 766 bx defN 26-May-17 13:22 OEBPS/title.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 46252 bx defN 26-May-17 13:22 OEBPS/tablesdatabases.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 12467 bx defN 26-May-17 13:22 OEBPS/system_principles.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 7347 bx defN 26-May-17 13:22 OEBPS/system_limits.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 63408 bx defN 26-May-17 13:22 OEBPS/sup_princ.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 251862 bx defN 26-May-17 13:22 OEBPS/statem.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 111634 bx defN 26-May-17 13:22 OEBPS/spec_proc.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 252193 bx defN 26-May-17 13:22 OEBPS/seq_prog.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 70944 bx defN 26-May-17 13:22 OEBPS/robustness.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 20878 bx defN 26-May-17 13:22 OEBPS/release_structure.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 61499 bx defN 26-May-17 13:22 OEBPS/release_handling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 4597 bx defN 26-May-17 13:22 OEBPS/reference_manual.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 19433 bx defN 26-May-17 13:22 OEBPS/ref_man_records.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 48277 bx defN 26-May-17 13:22 OEBPS/ref_man_processes.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 14478 bx defN 26-May-17 13:22 OEBPS/ref_man_functions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 49476 bx defN 26-May-17 13:22 OEBPS/records_macros.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2191 bx defN 26-May-17 13:22 OEBPS/readme.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 786 bx defN 26-May-17 13:22 OEBPS/programming_examples.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 40217 bx defN 26-May-17 13:22 OEBPS/prog_ex_records.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 15207 bx defN 26-May-17 13:22 OEBPS/profiling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 8502 bx defN 26-May-17 13:22 OEBPS/ports.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 3853 bx defN 26-May-17 13:22 OEBPS/patterns.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 13418 bx defN 26-May-17 13:22 OEBPS/overview.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 8960 bx defN 26-May-17 13:22 OEBPS/otp-patch-apply.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 9061 bx defN 26-May-17 13:22 OEBPS/opaques.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 21987 bx defN 26-May-17 13:22 OEBPS/nif.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 6368 bx defN 26-May-17 13:22 OEBPS/nav.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 26031 bx defN 26-May-17 13:22 OEBPS/modules.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 7013 bx defN 26-May-17 13:22 OEBPS/misc.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 5478 bx defN 26-May-17 13:22 OEBPS/memory.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 45533 bx defN 26-May-17 13:22 OEBPS/maps.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 39232 bx defN 26-May-17 13:22 OEBPS/macros.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 31302 bx defN 26-May-17 13:22 OEBPS/listhandling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 43066 bx defN 26-May-17 13:22 OEBPS/list_comprehensions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2212 bx defN 26-May-17 13:22 OEBPS/installation_guide.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 55528 bx defN 26-May-17 13:22 OEBPS/install.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 28230 bx defN 26-May-17 13:22 OEBPS/install-win32.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 35716 bx defN 26-May-17 13:22 OEBPS/install-cross.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 20859 bx defN 26-May-17 13:22 OEBPS/included_applications.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2354 bx defN 26-May-17 13:22 OEBPS/getting_started.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 31250 bx defN 26-May-17 13:22 OEBPS/gen_server_concepts.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 119111 bx defN 26-May-17 13:22 OEBPS/funs.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 8454 bx defN 26-May-17 13:22 OEBPS/features.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 256319 bx defN 26-May-17 13:22 OEBPS/expressions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 3572 bx defN 26-May-17 13:22 OEBPS/example.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 26587 bx defN 26-May-17 13:22 OEBPS/events.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 16653 bx defN 26-May-17 13:22 OEBPS/errors.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 13610 bx defN 26-May-17 13:22 OEBPS/error_logging.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 67865 bx defN 26-May-17 13:22 OEBPS/erl_interface.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 18221 bx defN 26-May-17 13:22 OEBPS/embedded.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 2086 bx defN 26-May-17 13:22 OEBPS/efficiency_guide.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 46585 bx defN 26-May-17 13:22 OEBPS/eff_guide_processes.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 21118 bx defN 26-May-17 13:22 OEBPS/eff_guide_functions.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 9295 bx defN 26-May-17 13:22 OEBPS/drivers.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 47181 bx defN 26-May-17 13:22 OEBPS/documentation.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 14893 bx defN 26-May-17 13:22 OEBPS/distributed_applications.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 24377 bx defN 26-May-17 13:22 OEBPS/distributed.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 14558 bx defN 26-May-17 13:22 OEBPS/dist/epub-erlang-QROVQRM3.css │ │ │ │ -?rw-r--r-- 6.1 unx 499 bx defN 26-May-17 13:22 OEBPS/dist/epub-LSJCIYTM.js │ │ │ │ -?rw-r--r-- 6.1 unx 36679 bx defN 26-May-17 13:22 OEBPS/design_principles.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 15004 bx defN 26-May-17 13:22 OEBPS/debugging.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 72831 bx defN 26-May-17 13:22 OEBPS/data_types.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 117715 bx defN 26-May-17 13:22 OEBPS/create_target.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 13188 bx defN 26-May-17 13:22 OEBPS/content.opf │ │ │ │ -?rw-r--r-- 6.1 unx 130016 bx defN 26-May-17 13:22 OEBPS/conc_prog.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 33299 bx defN 26-May-17 13:22 OEBPS/commoncaveats.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 11936 bx defN 26-May-17 13:22 OEBPS/code_loading.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 804 bx defN 26-May-17 13:22 OEBPS/cnode.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 5178 bx defN 26-May-17 13:22 OEBPS/character_set.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 51797 bx defN 26-May-17 13:22 OEBPS/c_portdriver.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 50622 bx defN 26-May-17 13:22 OEBPS/c_port.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 34776 bx defN 26-May-17 13:22 OEBPS/bit_syntax.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 53305 bx defN 26-May-17 13:22 OEBPS/binaryhandling.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 7607 bx defN 26-May-17 13:22 OEBPS/benchmarking.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 5837 bx defN 26-May-17 13:22 OEBPS/assets/logo.png │ │ │ │ -?rw-r--r-- 6.1 unx 5837 bx defN 26-May-17 13:22 OEBPS/assets/erlang-logo.png │ │ │ │ -?rw-r--r-- 6.1 unx 7044 bx stor 26-May-17 13:22 OEBPS/assets/dist5.gif │ │ │ │ -?rw-r--r-- 6.1 unx 2939 bx stor 26-May-17 13:22 OEBPS/assets/dist4.gif │ │ │ │ -?rw-r--r-- 6.1 unx 5007 bx stor 26-May-17 13:22 OEBPS/assets/dist3.gif │ │ │ │ -?rw-r--r-- 6.1 unx 5214 bx stor 26-May-17 13:22 OEBPS/assets/dist2.gif │ │ │ │ -?rw-r--r-- 6.1 unx 2284 bx stor 26-May-17 13:22 OEBPS/assets/dist1.gif │ │ │ │ -?rw-r--r-- 6.1 unx 7982 bx stor 26-May-17 13:22 OEBPS/assets/ballpoint-pen.svg │ │ │ │ -?rw-r--r-- 6.1 unx 104012 bx defN 26-May-17 13:22 OEBPS/appup_cookbook.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 47861 bx defN 26-May-17 13:22 OEBPS/applications.xhtml │ │ │ │ -?rw-r--r-- 6.1 unx 252 bx defN 26-May-17 13:22 META-INF/container.xml │ │ │ │ -?rw-r--r-- 6.1 unx 162 bx defN 26-May-17 13:22 META-INF/com.apple.ibooks.display-options.xml │ │ │ │ -91 files, 3158263 bytes uncompressed, 644511 bytes compressed: 79.6% │ │ │ │ +Zip file size: 660451 bytes, number of entries: 91 │ │ │ │ +?rw-r--r-- 6.1 unx 20 bx stor 26-May-17 15:33 mimetype │ │ │ │ +?rw-r--r-- 6.1 unx 17923 bx defN 26-May-17 15:33 OEBPS/versions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 4674 bx defN 26-May-17 15:33 OEBPS/upgrade.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 53442 bx defN 26-May-17 15:33 OEBPS/typespec.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2167 bx defN 26-May-17 15:33 OEBPS/tutorial.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 766 bx defN 26-May-17 15:33 OEBPS/title.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 46252 bx defN 26-May-17 15:33 OEBPS/tablesdatabases.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 12467 bx defN 26-May-17 15:33 OEBPS/system_principles.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 7347 bx defN 26-May-17 15:33 OEBPS/system_limits.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 63408 bx defN 26-May-17 15:33 OEBPS/sup_princ.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 251862 bx defN 26-May-17 15:33 OEBPS/statem.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 111634 bx defN 26-May-17 15:33 OEBPS/spec_proc.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 252193 bx defN 26-May-17 15:33 OEBPS/seq_prog.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 70944 bx defN 26-May-17 15:33 OEBPS/robustness.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 20878 bx defN 26-May-17 15:33 OEBPS/release_structure.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 61499 bx defN 26-May-17 15:33 OEBPS/release_handling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 4597 bx defN 26-May-17 15:33 OEBPS/reference_manual.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 19433 bx defN 26-May-17 15:33 OEBPS/ref_man_records.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 48277 bx defN 26-May-17 15:33 OEBPS/ref_man_processes.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 14478 bx defN 26-May-17 15:33 OEBPS/ref_man_functions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 49476 bx defN 26-May-17 15:33 OEBPS/records_macros.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2191 bx defN 26-May-17 15:33 OEBPS/readme.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 786 bx defN 26-May-17 15:33 OEBPS/programming_examples.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 40217 bx defN 26-May-17 15:33 OEBPS/prog_ex_records.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 15207 bx defN 26-May-17 15:33 OEBPS/profiling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 8502 bx defN 26-May-17 15:33 OEBPS/ports.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 3853 bx defN 26-May-17 15:33 OEBPS/patterns.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 13418 bx defN 26-May-17 15:33 OEBPS/overview.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 8960 bx defN 26-May-17 15:33 OEBPS/otp-patch-apply.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 9061 bx defN 26-May-17 15:33 OEBPS/opaques.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 21987 bx defN 26-May-17 15:33 OEBPS/nif.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 6368 bx defN 26-May-17 15:33 OEBPS/nav.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 26031 bx defN 26-May-17 15:33 OEBPS/modules.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 7013 bx defN 26-May-17 15:33 OEBPS/misc.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 5478 bx defN 26-May-17 15:33 OEBPS/memory.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 45533 bx defN 26-May-17 15:33 OEBPS/maps.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 39232 bx defN 26-May-17 15:33 OEBPS/macros.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 31302 bx defN 26-May-17 15:33 OEBPS/listhandling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 43066 bx defN 26-May-17 15:33 OEBPS/list_comprehensions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2212 bx defN 26-May-17 15:33 OEBPS/installation_guide.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 55528 bx defN 26-May-17 15:33 OEBPS/install.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 28230 bx defN 26-May-17 15:33 OEBPS/install-win32.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 35716 bx defN 26-May-17 15:33 OEBPS/install-cross.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 20859 bx defN 26-May-17 15:33 OEBPS/included_applications.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2354 bx defN 26-May-17 15:33 OEBPS/getting_started.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 31250 bx defN 26-May-17 15:33 OEBPS/gen_server_concepts.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 119111 bx defN 26-May-17 15:33 OEBPS/funs.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 8454 bx defN 26-May-17 15:33 OEBPS/features.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 256319 bx defN 26-May-17 15:33 OEBPS/expressions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 3572 bx defN 26-May-17 15:33 OEBPS/example.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 26587 bx defN 26-May-17 15:33 OEBPS/events.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 16653 bx defN 26-May-17 15:33 OEBPS/errors.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 13610 bx defN 26-May-17 15:33 OEBPS/error_logging.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 67865 bx defN 26-May-17 15:33 OEBPS/erl_interface.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 18221 bx defN 26-May-17 15:33 OEBPS/embedded.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 2086 bx defN 26-May-17 15:33 OEBPS/efficiency_guide.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 46585 bx defN 26-May-17 15:33 OEBPS/eff_guide_processes.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 21118 bx defN 26-May-17 15:33 OEBPS/eff_guide_functions.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 9295 bx defN 26-May-17 15:33 OEBPS/drivers.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 47181 bx defN 26-May-17 15:33 OEBPS/documentation.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 14893 bx defN 26-May-17 15:33 OEBPS/distributed_applications.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 24377 bx defN 26-May-17 15:33 OEBPS/distributed.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 14558 bx defN 26-May-17 15:33 OEBPS/dist/epub-erlang-QROVQRM3.css │ │ │ │ +?rw-r--r-- 6.1 unx 499 bx defN 26-May-17 15:33 OEBPS/dist/epub-LSJCIYTM.js │ │ │ │ +?rw-r--r-- 6.1 unx 36679 bx defN 26-May-17 15:33 OEBPS/design_principles.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 15004 bx defN 26-May-17 15:33 OEBPS/debugging.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 72831 bx defN 26-May-17 15:33 OEBPS/data_types.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 117715 bx defN 26-May-17 15:33 OEBPS/create_target.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 13188 bx defN 26-May-17 15:33 OEBPS/content.opf │ │ │ │ +?rw-r--r-- 6.1 unx 130016 bx defN 26-May-17 15:33 OEBPS/conc_prog.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 33299 bx defN 26-May-17 15:33 OEBPS/commoncaveats.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 11936 bx defN 26-May-17 15:33 OEBPS/code_loading.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 804 bx defN 26-May-17 15:33 OEBPS/cnode.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 5178 bx defN 26-May-17 15:33 OEBPS/character_set.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 51797 bx defN 26-May-17 15:33 OEBPS/c_portdriver.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 50622 bx defN 26-May-17 15:33 OEBPS/c_port.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 34776 bx defN 26-May-17 15:33 OEBPS/bit_syntax.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 53305 bx defN 26-May-17 15:33 OEBPS/binaryhandling.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 7607 bx defN 26-May-17 15:33 OEBPS/benchmarking.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 5837 bx defN 26-May-17 15:33 OEBPS/assets/logo.png │ │ │ │ +?rw-r--r-- 6.1 unx 5837 bx defN 26-May-17 15:33 OEBPS/assets/erlang-logo.png │ │ │ │ +?rw-r--r-- 6.1 unx 7044 bx stor 26-May-17 15:33 OEBPS/assets/dist5.gif │ │ │ │ +?rw-r--r-- 6.1 unx 2939 bx stor 26-May-17 15:33 OEBPS/assets/dist4.gif │ │ │ │ +?rw-r--r-- 6.1 unx 5007 bx stor 26-May-17 15:33 OEBPS/assets/dist3.gif │ │ │ │ +?rw-r--r-- 6.1 unx 5214 bx stor 26-May-17 15:33 OEBPS/assets/dist2.gif │ │ │ │ +?rw-r--r-- 6.1 unx 2284 bx stor 26-May-17 15:33 OEBPS/assets/dist1.gif │ │ │ │ +?rw-r--r-- 6.1 unx 7982 bx stor 26-May-17 15:33 OEBPS/assets/ballpoint-pen.svg │ │ │ │ +?rw-r--r-- 6.1 unx 104012 bx defN 26-May-17 15:33 OEBPS/appup_cookbook.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 47861 bx defN 26-May-17 15:33 OEBPS/applications.xhtml │ │ │ │ +?rw-r--r-- 6.1 unx 252 bx defN 26-May-17 15:33 META-INF/container.xml │ │ │ │ +?rw-r--r-- 6.1 unx 162 bx defN 26-May-17 15:33 META-INF/com.apple.ibooks.display-options.xml │ │ │ │ +91 files, 3158263 bytes uncompressed, 644519 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 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ +0000A Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ 0000E CRC 2CAB616F (749429103) │ │ │ │ 00012 Compressed Size 00000014 (20) │ │ │ │ 00016 Uncompressed Size 00000014 (20) │ │ │ │ 0001A Filename Length 0008 (8) │ │ │ │ 0001C Extra Length 001C (28) │ │ │ │ 0001E Filename 'XXXXXXXX' │ │ │ │ # │ │ │ │ # WARNING: Offset 0x1E: Filename 'XXXXXXXX' │ │ │ │ # Zero length filename │ │ │ │ # │ │ │ │ 00026 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ 00028 Length 0009 (9) │ │ │ │ 0002A Flags 03 (3) 'Modification Access' │ │ │ │ -0002B Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -0002F Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ +0002B Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +0002F Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ 00033 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ 00035 Length 000B (11) │ │ │ │ 00037 Version 01 (1) │ │ │ │ 00038 UID Size 04 (4) │ │ │ │ 00039 UID 00000000 (0) │ │ │ │ 0003D GID Size 04 (4) │ │ │ │ 0003E GID 00000000 (0) │ │ │ │ @@ -31,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 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ +00060 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 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 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -00091 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ +0008D Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +00091 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 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 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ +0165B Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 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 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -0168B Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ +01687 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +0168B Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 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 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -01D82 CRC 3CA07DB0 (1017150896) │ │ │ │ -01D86 Compressed Size 00002DA7 (11687) │ │ │ │ +01D7E Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +01D82 CRC 6F43BBC2 (1866709954) │ │ │ │ +01D86 Compressed Size 00002DAB (11691) │ │ │ │ 01D8A Uncompressed Size 0000D0C2 (53442) │ │ │ │ 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 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -01DAF Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ +01DAB Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +01DAF Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 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 │ │ │ │ │ │ │ │ -04B69 LOCAL HEADER #5 04034B50 (67324752) │ │ │ │ -04B6D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -04B6E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -04B6F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -04B71 Compression Method 0008 (8) 'Deflated' │ │ │ │ -04B73 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -04B77 CRC AFEB34EA (2951427306) │ │ │ │ -04B7B Compressed Size 000003F0 (1008) │ │ │ │ -04B7F Uncompressed Size 00000877 (2167) │ │ │ │ -04B83 Filename Length 0014 (20) │ │ │ │ -04B85 Extra Length 001C (28) │ │ │ │ -04B87 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4B87: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -04B9B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -04B9D Length 0009 (9) │ │ │ │ -04B9F Flags 03 (3) 'Modification Access' │ │ │ │ -04BA0 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -04BA4 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -04BA8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -04BAA Length 000B (11) │ │ │ │ -04BAC Version 01 (1) │ │ │ │ -04BAD UID Size 04 (4) │ │ │ │ -04BAE UID 00000000 (0) │ │ │ │ -04BB2 GID Size 04 (4) │ │ │ │ -04BB3 GID 00000000 (0) │ │ │ │ -04BB7 PAYLOAD │ │ │ │ - │ │ │ │ -04FA7 LOCAL HEADER #6 04034B50 (67324752) │ │ │ │ -04FAB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -04FAC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -04FAD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -04FAF Compression Method 0008 (8) 'Deflated' │ │ │ │ -04FB1 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -04FB5 CRC 21AF85A7 (565151143) │ │ │ │ -04FB9 Compressed Size 000001AE (430) │ │ │ │ -04FBD Uncompressed Size 000002FE (766) │ │ │ │ -04FC1 Filename Length 0011 (17) │ │ │ │ -04FC3 Extra Length 001C (28) │ │ │ │ -04FC5 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4FC5: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -04FD6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -04FD8 Length 0009 (9) │ │ │ │ -04FDA Flags 03 (3) 'Modification Access' │ │ │ │ -04FDB Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -04FDF Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -04FE3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -04FE5 Length 000B (11) │ │ │ │ -04FE7 Version 01 (1) │ │ │ │ -04FE8 UID Size 04 (4) │ │ │ │ -04FE9 UID 00000000 (0) │ │ │ │ -04FED GID Size 04 (4) │ │ │ │ -04FEE GID 00000000 (0) │ │ │ │ -04FF2 PAYLOAD │ │ │ │ - │ │ │ │ -051A0 LOCAL HEADER #7 04034B50 (67324752) │ │ │ │ -051A4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -051A5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -051A6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -051A8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -051AA Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -051AE CRC 0C5C4C93 (207375507) │ │ │ │ -051B2 Compressed Size 000020BD (8381) │ │ │ │ -051B6 Uncompressed Size 0000B4AC (46252) │ │ │ │ -051BA Filename Length 001B (27) │ │ │ │ -051BC Extra Length 001C (28) │ │ │ │ -051BE Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x51BE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -051D9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -051DB Length 0009 (9) │ │ │ │ -051DD Flags 03 (3) 'Modification Access' │ │ │ │ -051DE Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -051E2 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -051E6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -051E8 Length 000B (11) │ │ │ │ -051EA Version 01 (1) │ │ │ │ -051EB UID Size 04 (4) │ │ │ │ -051EC UID 00000000 (0) │ │ │ │ -051F0 GID Size 04 (4) │ │ │ │ -051F1 GID 00000000 (0) │ │ │ │ -051F5 PAYLOAD │ │ │ │ - │ │ │ │ -072B2 LOCAL HEADER #8 04034B50 (67324752) │ │ │ │ -072B6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -072B7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -072B8 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -072BA Compression Method 0008 (8) 'Deflated' │ │ │ │ -072BC Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -072C0 CRC 2D371F1E (758587166) │ │ │ │ -072C4 Compressed Size 00000E70 (3696) │ │ │ │ -072C8 Uncompressed Size 000030B3 (12467) │ │ │ │ -072CC Filename Length 001D (29) │ │ │ │ -072CE Extra Length 001C (28) │ │ │ │ -072D0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x72D0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -072ED Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -072EF Length 0009 (9) │ │ │ │ -072F1 Flags 03 (3) 'Modification Access' │ │ │ │ -072F2 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -072F6 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -072FA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -072FC Length 000B (11) │ │ │ │ -072FE Version 01 (1) │ │ │ │ -072FF UID Size 04 (4) │ │ │ │ -07300 UID 00000000 (0) │ │ │ │ -07304 GID Size 04 (4) │ │ │ │ -07305 GID 00000000 (0) │ │ │ │ -07309 PAYLOAD │ │ │ │ - │ │ │ │ -08179 LOCAL HEADER #9 04034B50 (67324752) │ │ │ │ -0817D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -0817E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -0817F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -08181 Compression Method 0008 (8) 'Deflated' │ │ │ │ -08183 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -08187 CRC 7903D0CB (2030293195) │ │ │ │ -0818B Compressed Size 00000973 (2419) │ │ │ │ -0818F Uncompressed Size 00001CB3 (7347) │ │ │ │ -08193 Filename Length 0019 (25) │ │ │ │ -08195 Extra Length 001C (28) │ │ │ │ -08197 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8197: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -081B0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -081B2 Length 0009 (9) │ │ │ │ -081B4 Flags 03 (3) 'Modification Access' │ │ │ │ -081B5 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -081B9 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -081BD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -081BF Length 000B (11) │ │ │ │ -081C1 Version 01 (1) │ │ │ │ -081C2 UID Size 04 (4) │ │ │ │ -081C3 UID 00000000 (0) │ │ │ │ -081C7 GID Size 04 (4) │ │ │ │ -081C8 GID 00000000 (0) │ │ │ │ -081CC PAYLOAD │ │ │ │ - │ │ │ │ -08B3F LOCAL HEADER #10 04034B50 (67324752) │ │ │ │ -08B43 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -08B44 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -08B45 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -08B47 Compression Method 0008 (8) 'Deflated' │ │ │ │ -08B49 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -08B4D CRC 68A2F74B (1755510603) │ │ │ │ -08B51 Compressed Size 00003864 (14436) │ │ │ │ -08B55 Uncompressed Size 0000F7B0 (63408) │ │ │ │ -08B59 Filename Length 0015 (21) │ │ │ │ -08B5B Extra Length 001C (28) │ │ │ │ -08B5D Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8B5D: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -08B72 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -08B74 Length 0009 (9) │ │ │ │ -08B76 Flags 03 (3) 'Modification Access' │ │ │ │ -08B77 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -08B7B Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -08B7F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -08B81 Length 000B (11) │ │ │ │ -08B83 Version 01 (1) │ │ │ │ -08B84 UID Size 04 (4) │ │ │ │ -08B85 UID 00000000 (0) │ │ │ │ -08B89 GID Size 04 (4) │ │ │ │ -08B8A GID 00000000 (0) │ │ │ │ -08B8E PAYLOAD │ │ │ │ - │ │ │ │ -0C3F2 LOCAL HEADER #11 04034B50 (67324752) │ │ │ │ -0C3F6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -0C3F7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -0C3F8 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -0C3FA Compression Method 0008 (8) 'Deflated' │ │ │ │ -0C3FC Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -0C400 CRC 3DCFFD78 (1037041016) │ │ │ │ -0C404 Compressed Size 0000A9BA (43450) │ │ │ │ -0C408 Uncompressed Size 0003D7D6 (251862) │ │ │ │ -0C40C Filename Length 0012 (18) │ │ │ │ -0C40E Extra Length 001C (28) │ │ │ │ -0C410 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xC410: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -0C422 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -0C424 Length 0009 (9) │ │ │ │ -0C426 Flags 03 (3) 'Modification Access' │ │ │ │ -0C427 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -0C42B Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -0C42F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -0C431 Length 000B (11) │ │ │ │ -0C433 Version 01 (1) │ │ │ │ -0C434 UID Size 04 (4) │ │ │ │ -0C435 UID 00000000 (0) │ │ │ │ -0C439 GID Size 04 (4) │ │ │ │ -0C43A GID 00000000 (0) │ │ │ │ -0C43E PAYLOAD │ │ │ │ - │ │ │ │ -16DF8 LOCAL HEADER #12 04034B50 (67324752) │ │ │ │ -16DFC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -16DFD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -16DFE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -16E00 Compression Method 0008 (8) 'Deflated' │ │ │ │ -16E02 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -16E06 CRC 409E09C8 (1084099016) │ │ │ │ -16E0A Compressed Size 00003B28 (15144) │ │ │ │ -16E0E Uncompressed Size 0001B412 (111634) │ │ │ │ -16E12 Filename Length 0015 (21) │ │ │ │ -16E14 Extra Length 001C (28) │ │ │ │ -16E16 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x16E16: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -16E2B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -16E2D Length 0009 (9) │ │ │ │ -16E2F Flags 03 (3) 'Modification Access' │ │ │ │ -16E30 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -16E34 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -16E38 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -16E3A Length 000B (11) │ │ │ │ -16E3C Version 01 (1) │ │ │ │ -16E3D UID Size 04 (4) │ │ │ │ -16E3E UID 00000000 (0) │ │ │ │ -16E42 GID Size 04 (4) │ │ │ │ -16E43 GID 00000000 (0) │ │ │ │ -16E47 PAYLOAD │ │ │ │ - │ │ │ │ -1A96F LOCAL HEADER #13 04034B50 (67324752) │ │ │ │ -1A973 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -1A974 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -1A975 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -1A977 Compression Method 0008 (8) 'Deflated' │ │ │ │ -1A979 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -1A97D CRC 6929B99A (1764342170) │ │ │ │ -1A981 Compressed Size 00009166 (37222) │ │ │ │ -1A985 Uncompressed Size 0003D921 (252193) │ │ │ │ -1A989 Filename Length 0014 (20) │ │ │ │ -1A98B Extra Length 001C (28) │ │ │ │ -1A98D Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x1A98D: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -1A9A1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -1A9A3 Length 0009 (9) │ │ │ │ -1A9A5 Flags 03 (3) 'Modification Access' │ │ │ │ -1A9A6 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -1A9AA Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -1A9AE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -1A9B0 Length 000B (11) │ │ │ │ -1A9B2 Version 01 (1) │ │ │ │ -1A9B3 UID Size 04 (4) │ │ │ │ -1A9B4 UID 00000000 (0) │ │ │ │ -1A9B8 GID Size 04 (4) │ │ │ │ -1A9B9 GID 00000000 (0) │ │ │ │ -1A9BD PAYLOAD │ │ │ │ - │ │ │ │ -23B23 LOCAL HEADER #14 04034B50 (67324752) │ │ │ │ -23B27 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -23B28 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -23B29 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -23B2B Compression Method 0008 (8) 'Deflated' │ │ │ │ -23B2D Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -23B31 CRC D237ECE3 (3526880483) │ │ │ │ -23B35 Compressed Size 00002A76 (10870) │ │ │ │ -23B39 Uncompressed Size 00011520 (70944) │ │ │ │ -23B3D Filename Length 0016 (22) │ │ │ │ -23B3F Extra Length 001C (28) │ │ │ │ -23B41 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x23B41: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -23B57 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -23B59 Length 0009 (9) │ │ │ │ -23B5B Flags 03 (3) 'Modification Access' │ │ │ │ -23B5C Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -23B60 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -23B64 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -23B66 Length 000B (11) │ │ │ │ -23B68 Version 01 (1) │ │ │ │ -23B69 UID Size 04 (4) │ │ │ │ -23B6A UID 00000000 (0) │ │ │ │ -23B6E GID Size 04 (4) │ │ │ │ -23B6F GID 00000000 (0) │ │ │ │ -23B73 PAYLOAD │ │ │ │ - │ │ │ │ -265E9 LOCAL HEADER #15 04034B50 (67324752) │ │ │ │ -265ED Extract Zip Spec 14 (20) '2.0' │ │ │ │ -265EE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -265EF General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -265F1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -265F3 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -265F7 CRC AAAA060E (2863269390) │ │ │ │ -265FB Compressed Size 000014DF (5343) │ │ │ │ -265FF Uncompressed Size 0000518E (20878) │ │ │ │ -26603 Filename Length 001D (29) │ │ │ │ -26605 Extra Length 001C (28) │ │ │ │ -26607 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x26607: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -26624 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -26626 Length 0009 (9) │ │ │ │ -26628 Flags 03 (3) 'Modification Access' │ │ │ │ -26629 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -2662D Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -26631 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -26633 Length 000B (11) │ │ │ │ -26635 Version 01 (1) │ │ │ │ -26636 UID Size 04 (4) │ │ │ │ -26637 UID 00000000 (0) │ │ │ │ -2663B GID Size 04 (4) │ │ │ │ -2663C GID 00000000 (0) │ │ │ │ -26640 PAYLOAD │ │ │ │ - │ │ │ │ -27B1F LOCAL HEADER #16 04034B50 (67324752) │ │ │ │ -27B23 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -27B24 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -27B25 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -27B27 Compression Method 0008 (8) 'Deflated' │ │ │ │ -27B29 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -27B2D CRC 878C1C89 (2274106505) │ │ │ │ -27B31 Compressed Size 000039CB (14795) │ │ │ │ -27B35 Uncompressed Size 0000F03B (61499) │ │ │ │ -27B39 Filename Length 001C (28) │ │ │ │ -27B3B Extra Length 001C (28) │ │ │ │ -27B3D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x27B3D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -27B59 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -27B5B Length 0009 (9) │ │ │ │ -27B5D Flags 03 (3) 'Modification Access' │ │ │ │ -27B5E Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -27B62 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -27B66 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -27B68 Length 000B (11) │ │ │ │ -27B6A Version 01 (1) │ │ │ │ -27B6B UID Size 04 (4) │ │ │ │ -27B6C UID 00000000 (0) │ │ │ │ -27B70 GID Size 04 (4) │ │ │ │ -27B71 GID 00000000 (0) │ │ │ │ -27B75 PAYLOAD │ │ │ │ - │ │ │ │ -2B540 LOCAL HEADER #17 04034B50 (67324752) │ │ │ │ -2B544 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -2B545 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -2B546 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -2B548 Compression Method 0008 (8) 'Deflated' │ │ │ │ -2B54A Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -2B54E CRC 7A88ED58 (2055794008) │ │ │ │ -2B552 Compressed Size 000006A3 (1699) │ │ │ │ -2B556 Uncompressed Size 000011F5 (4597) │ │ │ │ -2B55A Filename Length 001C (28) │ │ │ │ -2B55C Extra Length 001C (28) │ │ │ │ -2B55E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x2B55E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -2B57A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -2B57C Length 0009 (9) │ │ │ │ -2B57E Flags 03 (3) 'Modification Access' │ │ │ │ -2B57F Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -2B583 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -2B587 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -2B589 Length 000B (11) │ │ │ │ -2B58B Version 01 (1) │ │ │ │ -2B58C UID Size 04 (4) │ │ │ │ -2B58D UID 00000000 (0) │ │ │ │ -2B591 GID Size 04 (4) │ │ │ │ -2B592 GID 00000000 (0) │ │ │ │ -2B596 PAYLOAD │ │ │ │ - │ │ │ │ -2BC39 LOCAL HEADER #18 04034B50 (67324752) │ │ │ │ -2BC3D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -2BC3E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -2BC3F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -2BC41 Compression Method 0008 (8) 'Deflated' │ │ │ │ -2BC43 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -2BC47 CRC 5F03D695 (1594087061) │ │ │ │ -2BC4B Compressed Size 0000107F (4223) │ │ │ │ -2BC4F Uncompressed Size 00004BE9 (19433) │ │ │ │ -2BC53 Filename Length 001B (27) │ │ │ │ -2BC55 Extra Length 001C (28) │ │ │ │ -2BC57 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x2BC57: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -2BC72 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -2BC74 Length 0009 (9) │ │ │ │ -2BC76 Flags 03 (3) 'Modification Access' │ │ │ │ -2BC77 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -2BC7B Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -2BC7F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -2BC81 Length 000B (11) │ │ │ │ -2BC83 Version 01 (1) │ │ │ │ -2BC84 UID Size 04 (4) │ │ │ │ -2BC85 UID 00000000 (0) │ │ │ │ -2BC89 GID Size 04 (4) │ │ │ │ -2BC8A GID 00000000 (0) │ │ │ │ -2BC8E PAYLOAD │ │ │ │ - │ │ │ │ -2CD0D LOCAL HEADER #19 04034B50 (67324752) │ │ │ │ -2CD11 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -2CD12 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -2CD13 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -2CD15 Compression Method 0008 (8) 'Deflated' │ │ │ │ -2CD17 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -2CD1B CRC 54D35332 (1423135538) │ │ │ │ -2CD1F Compressed Size 000033AD (13229) │ │ │ │ -2CD23 Uncompressed Size 0000BC95 (48277) │ │ │ │ -2CD27 Filename Length 001D (29) │ │ │ │ -2CD29 Extra Length 001C (28) │ │ │ │ -2CD2B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x2CD2B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -2CD48 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -2CD4A Length 0009 (9) │ │ │ │ -2CD4C Flags 03 (3) 'Modification Access' │ │ │ │ -2CD4D Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -2CD51 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -2CD55 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -2CD57 Length 000B (11) │ │ │ │ -2CD59 Version 01 (1) │ │ │ │ -2CD5A UID Size 04 (4) │ │ │ │ -2CD5B UID 00000000 (0) │ │ │ │ -2CD5F GID Size 04 (4) │ │ │ │ -2CD60 GID 00000000 (0) │ │ │ │ -2CD64 PAYLOAD │ │ │ │ - │ │ │ │ -30111 LOCAL HEADER #20 04034B50 (67324752) │ │ │ │ -30115 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -30116 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -30117 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -30119 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3011B Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -3011F CRC 355C0CEA (895225066) │ │ │ │ -30123 Compressed Size 00000D6E (3438) │ │ │ │ -30127 Uncompressed Size 0000388E (14478) │ │ │ │ -3012B Filename Length 001D (29) │ │ │ │ -3012D Extra Length 001C (28) │ │ │ │ -3012F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3012F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3014C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3014E Length 0009 (9) │ │ │ │ -30150 Flags 03 (3) 'Modification Access' │ │ │ │ -30151 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -30155 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -30159 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3015B Length 000B (11) │ │ │ │ -3015D Version 01 (1) │ │ │ │ -3015E UID Size 04 (4) │ │ │ │ -3015F UID 00000000 (0) │ │ │ │ -30163 GID Size 04 (4) │ │ │ │ -30164 GID 00000000 (0) │ │ │ │ -30168 PAYLOAD │ │ │ │ - │ │ │ │ -30ED6 LOCAL HEADER #21 04034B50 (67324752) │ │ │ │ -30EDA Extract Zip Spec 14 (20) '2.0' │ │ │ │ -30EDB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -30EDC General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -30EDE Compression Method 0008 (8) 'Deflated' │ │ │ │ -30EE0 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -30EE4 CRC 99A8338F (2577937295) │ │ │ │ -30EE8 Compressed Size 00001C7D (7293) │ │ │ │ -30EEC Uncompressed Size 0000C144 (49476) │ │ │ │ -30EF0 Filename Length 001A (26) │ │ │ │ -30EF2 Extra Length 001C (28) │ │ │ │ -30EF4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x30EF4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -30F0E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -30F10 Length 0009 (9) │ │ │ │ -30F12 Flags 03 (3) 'Modification Access' │ │ │ │ -30F13 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -30F17 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -30F1B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -30F1D Length 000B (11) │ │ │ │ -30F1F Version 01 (1) │ │ │ │ -30F20 UID Size 04 (4) │ │ │ │ -30F21 UID 00000000 (0) │ │ │ │ -30F25 GID Size 04 (4) │ │ │ │ -30F26 GID 00000000 (0) │ │ │ │ -30F2A PAYLOAD │ │ │ │ - │ │ │ │ -32BA7 LOCAL HEADER #22 04034B50 (67324752) │ │ │ │ -32BAB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -32BAC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -32BAD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -32BAF Compression Method 0008 (8) 'Deflated' │ │ │ │ -32BB1 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -32BB5 CRC C41B95CB (3290142155) │ │ │ │ -32BB9 Compressed Size 000003A4 (932) │ │ │ │ -32BBD Uncompressed Size 0000088F (2191) │ │ │ │ -32BC1 Filename Length 0012 (18) │ │ │ │ -32BC3 Extra Length 001C (28) │ │ │ │ -32BC5 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x32BC5: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -32BD7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -32BD9 Length 0009 (9) │ │ │ │ -32BDB Flags 03 (3) 'Modification Access' │ │ │ │ -32BDC Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -32BE0 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -32BE4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -32BE6 Length 000B (11) │ │ │ │ -32BE8 Version 01 (1) │ │ │ │ -32BE9 UID Size 04 (4) │ │ │ │ -32BEA UID 00000000 (0) │ │ │ │ -32BEE GID Size 04 (4) │ │ │ │ -32BEF GID 00000000 (0) │ │ │ │ -32BF3 PAYLOAD │ │ │ │ - │ │ │ │ -32F97 LOCAL HEADER #23 04034B50 (67324752) │ │ │ │ -32F9B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -32F9C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -32F9D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -32F9F Compression Method 0008 (8) 'Deflated' │ │ │ │ -32FA1 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -32FA5 CRC 40B68220 (1085702688) │ │ │ │ -32FA9 Compressed Size 000001D4 (468) │ │ │ │ -32FAD Uncompressed Size 00000312 (786) │ │ │ │ -32FB1 Filename Length 0020 (32) │ │ │ │ -32FB3 Extra Length 001C (28) │ │ │ │ -32FB5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x32FB5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -32FD5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -32FD7 Length 0009 (9) │ │ │ │ -32FD9 Flags 03 (3) 'Modification Access' │ │ │ │ -32FDA Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -32FDE Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -32FE2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -32FE4 Length 000B (11) │ │ │ │ -32FE6 Version 01 (1) │ │ │ │ -32FE7 UID Size 04 (4) │ │ │ │ -32FE8 UID 00000000 (0) │ │ │ │ -32FEC GID Size 04 (4) │ │ │ │ -32FED GID 00000000 (0) │ │ │ │ -32FF1 PAYLOAD │ │ │ │ - │ │ │ │ -331C5 LOCAL HEADER #24 04034B50 (67324752) │ │ │ │ -331C9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -331CA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -331CB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -331CD Compression Method 0008 (8) 'Deflated' │ │ │ │ -331CF Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -331D3 CRC 6B5C6C16 (1801219094) │ │ │ │ -331D7 Compressed Size 000017AC (6060) │ │ │ │ -331DB Uncompressed Size 00009D19 (40217) │ │ │ │ -331DF Filename Length 001B (27) │ │ │ │ -331E1 Extra Length 001C (28) │ │ │ │ -331E3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x331E3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -331FE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -33200 Length 0009 (9) │ │ │ │ -33202 Flags 03 (3) 'Modification Access' │ │ │ │ -33203 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -33207 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -3320B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3320D Length 000B (11) │ │ │ │ -3320F Version 01 (1) │ │ │ │ -33210 UID Size 04 (4) │ │ │ │ -33211 UID 00000000 (0) │ │ │ │ -33215 GID Size 04 (4) │ │ │ │ -33216 GID 00000000 (0) │ │ │ │ -3321A PAYLOAD │ │ │ │ - │ │ │ │ -349C6 LOCAL HEADER #25 04034B50 (67324752) │ │ │ │ -349CA Extract Zip Spec 14 (20) '2.0' │ │ │ │ -349CB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -349CC General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -349CE Compression Method 0008 (8) 'Deflated' │ │ │ │ -349D0 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -349D4 CRC 8B976C32 (2341956658) │ │ │ │ -349D8 Compressed Size 00001374 (4980) │ │ │ │ -349DC Uncompressed Size 00003B67 (15207) │ │ │ │ -349E0 Filename Length 0015 (21) │ │ │ │ -349E2 Extra Length 001C (28) │ │ │ │ -349E4 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x349E4: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -349F9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -349FB Length 0009 (9) │ │ │ │ -349FD Flags 03 (3) 'Modification Access' │ │ │ │ -349FE Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -34A02 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -34A06 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -34A08 Length 000B (11) │ │ │ │ -34A0A Version 01 (1) │ │ │ │ -34A0B UID Size 04 (4) │ │ │ │ -34A0C UID 00000000 (0) │ │ │ │ -34A10 GID Size 04 (4) │ │ │ │ -34A11 GID 00000000 (0) │ │ │ │ -34A15 PAYLOAD │ │ │ │ - │ │ │ │ -35D89 LOCAL HEADER #26 04034B50 (67324752) │ │ │ │ -35D8D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -35D8E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -35D8F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -35D91 Compression Method 0008 (8) 'Deflated' │ │ │ │ -35D93 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -35D97 CRC 2ACCA91C (718055708) │ │ │ │ -35D9B Compressed Size 00000AD3 (2771) │ │ │ │ -35D9F Uncompressed Size 00002136 (8502) │ │ │ │ -35DA3 Filename Length 0011 (17) │ │ │ │ -35DA5 Extra Length 001C (28) │ │ │ │ -35DA7 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x35DA7: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -35DB8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -35DBA Length 0009 (9) │ │ │ │ -35DBC Flags 03 (3) 'Modification Access' │ │ │ │ -35DBD Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -35DC1 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -35DC5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -35DC7 Length 000B (11) │ │ │ │ -35DC9 Version 01 (1) │ │ │ │ -35DCA UID Size 04 (4) │ │ │ │ -35DCB UID 00000000 (0) │ │ │ │ -35DCF GID Size 04 (4) │ │ │ │ -35DD0 GID 00000000 (0) │ │ │ │ -35DD4 PAYLOAD │ │ │ │ - │ │ │ │ -368A7 LOCAL HEADER #27 04034B50 (67324752) │ │ │ │ -368AB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -368AC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -368AD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -368AF Compression Method 0008 (8) 'Deflated' │ │ │ │ -368B1 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -368B5 CRC 4C2A379D (1277835165) │ │ │ │ -368B9 Compressed Size 000003FE (1022) │ │ │ │ -368BD Uncompressed Size 00000F0D (3853) │ │ │ │ -368C1 Filename Length 0014 (20) │ │ │ │ -368C3 Extra Length 001C (28) │ │ │ │ -368C5 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x368C5: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -368D9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -368DB Length 0009 (9) │ │ │ │ -368DD Flags 03 (3) 'Modification Access' │ │ │ │ -368DE Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -368E2 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -368E6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -368E8 Length 000B (11) │ │ │ │ -368EA Version 01 (1) │ │ │ │ -368EB UID Size 04 (4) │ │ │ │ -368EC UID 00000000 (0) │ │ │ │ -368F0 GID Size 04 (4) │ │ │ │ -368F1 GID 00000000 (0) │ │ │ │ -368F5 PAYLOAD │ │ │ │ - │ │ │ │ -36CF3 LOCAL HEADER #28 04034B50 (67324752) │ │ │ │ -36CF7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -36CF8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -36CF9 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -36CFB Compression Method 0008 (8) 'Deflated' │ │ │ │ -36CFD Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -36D01 CRC F2ED2F43 (4075630403) │ │ │ │ -36D05 Compressed Size 00001263 (4707) │ │ │ │ -36D09 Uncompressed Size 0000346A (13418) │ │ │ │ -36D0D Filename Length 0014 (20) │ │ │ │ -36D0F Extra Length 001C (28) │ │ │ │ -36D11 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x36D11: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -36D25 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -36D27 Length 0009 (9) │ │ │ │ -36D29 Flags 03 (3) 'Modification Access' │ │ │ │ -36D2A Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -36D2E Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -36D32 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -36D34 Length 000B (11) │ │ │ │ -36D36 Version 01 (1) │ │ │ │ -36D37 UID Size 04 (4) │ │ │ │ -36D38 UID 00000000 (0) │ │ │ │ -36D3C GID Size 04 (4) │ │ │ │ -36D3D GID 00000000 (0) │ │ │ │ -36D41 PAYLOAD │ │ │ │ - │ │ │ │ -37FA4 LOCAL HEADER #29 04034B50 (67324752) │ │ │ │ -37FA8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -37FA9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -37FAA General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -37FAC Compression Method 0008 (8) 'Deflated' │ │ │ │ -37FAE Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -37FB2 CRC 6E1AD5C6 (1847252422) │ │ │ │ -37FB6 Compressed Size 00000AD0 (2768) │ │ │ │ -37FBA Uncompressed Size 00002300 (8960) │ │ │ │ -37FBE Filename Length 001B (27) │ │ │ │ -37FC0 Extra Length 001C (28) │ │ │ │ -37FC2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x37FC2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -37FDD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -37FDF Length 0009 (9) │ │ │ │ -37FE1 Flags 03 (3) 'Modification Access' │ │ │ │ -37FE2 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -37FE6 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -37FEA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -37FEC Length 000B (11) │ │ │ │ -37FEE Version 01 (1) │ │ │ │ -37FEF UID Size 04 (4) │ │ │ │ -37FF0 UID 00000000 (0) │ │ │ │ -37FF4 GID Size 04 (4) │ │ │ │ -37FF5 GID 00000000 (0) │ │ │ │ -37FF9 PAYLOAD │ │ │ │ - │ │ │ │ -38AC9 LOCAL HEADER #30 04034B50 (67324752) │ │ │ │ -38ACD Extract Zip Spec 14 (20) '2.0' │ │ │ │ -38ACE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -38ACF General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -38AD1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -38AD3 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -38AD7 CRC F988B6A5 (4186486437) │ │ │ │ -38ADB Compressed Size 00000A93 (2707) │ │ │ │ -38ADF Uncompressed Size 00002365 (9061) │ │ │ │ -38AE3 Filename Length 0013 (19) │ │ │ │ -38AE5 Extra Length 001C (28) │ │ │ │ -38AE7 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x38AE7: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -38AFA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -38AFC Length 0009 (9) │ │ │ │ -38AFE Flags 03 (3) 'Modification Access' │ │ │ │ -38AFF Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -38B03 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -38B07 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -38B09 Length 000B (11) │ │ │ │ -38B0B Version 01 (1) │ │ │ │ -38B0C UID Size 04 (4) │ │ │ │ -38B0D UID 00000000 (0) │ │ │ │ -38B11 GID Size 04 (4) │ │ │ │ -38B12 GID 00000000 (0) │ │ │ │ -38B16 PAYLOAD │ │ │ │ - │ │ │ │ -395A9 LOCAL HEADER #31 04034B50 (67324752) │ │ │ │ -395AD Extract Zip Spec 14 (20) '2.0' │ │ │ │ -395AE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -395AF General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -395B1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -395B3 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -395B7 CRC A8B2957D (2830275965) │ │ │ │ -395BB Compressed Size 000010DB (4315) │ │ │ │ -395BF Uncompressed Size 000055E3 (21987) │ │ │ │ -395C3 Filename Length 000F (15) │ │ │ │ -395C5 Extra Length 001C (28) │ │ │ │ -395C7 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x395C7: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -395D6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -395D8 Length 0009 (9) │ │ │ │ -395DA Flags 03 (3) 'Modification Access' │ │ │ │ -395DB Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -395DF Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -395E3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -395E5 Length 000B (11) │ │ │ │ -395E7 Version 01 (1) │ │ │ │ -395E8 UID Size 04 (4) │ │ │ │ -395E9 UID 00000000 (0) │ │ │ │ -395ED GID Size 04 (4) │ │ │ │ -395EE GID 00000000 (0) │ │ │ │ -395F2 PAYLOAD │ │ │ │ - │ │ │ │ -3A6CD LOCAL HEADER #32 04034B50 (67324752) │ │ │ │ -3A6D1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3A6D2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3A6D3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3A6D5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3A6D7 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -3A6DB CRC C70CE575 (3339511157) │ │ │ │ -3A6DF Compressed Size 0000066B (1643) │ │ │ │ -3A6E3 Uncompressed Size 000018E0 (6368) │ │ │ │ -3A6E7 Filename Length 000F (15) │ │ │ │ -3A6E9 Extra Length 001C (28) │ │ │ │ -3A6EB Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3A6EB: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3A6FA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3A6FC Length 0009 (9) │ │ │ │ -3A6FE Flags 03 (3) 'Modification Access' │ │ │ │ -3A6FF Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -3A703 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -3A707 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3A709 Length 000B (11) │ │ │ │ -3A70B Version 01 (1) │ │ │ │ -3A70C UID Size 04 (4) │ │ │ │ -3A70D UID 00000000 (0) │ │ │ │ -3A711 GID Size 04 (4) │ │ │ │ -3A712 GID 00000000 (0) │ │ │ │ -3A716 PAYLOAD │ │ │ │ - │ │ │ │ -3AD81 LOCAL HEADER #33 04034B50 (67324752) │ │ │ │ -3AD85 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3AD86 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3AD87 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3AD89 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3AD8B Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -3AD8F CRC 3F62BBB5 (1063435189) │ │ │ │ -3AD93 Compressed Size 00001A60 (6752) │ │ │ │ -3AD97 Uncompressed Size 000065AF (26031) │ │ │ │ -3AD9B Filename Length 0013 (19) │ │ │ │ -3AD9D Extra Length 001C (28) │ │ │ │ -3AD9F Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3AD9F: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3ADB2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3ADB4 Length 0009 (9) │ │ │ │ -3ADB6 Flags 03 (3) 'Modification Access' │ │ │ │ -3ADB7 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -3ADBB Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -3ADBF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3ADC1 Length 000B (11) │ │ │ │ -3ADC3 Version 01 (1) │ │ │ │ -3ADC4 UID Size 04 (4) │ │ │ │ -3ADC5 UID 00000000 (0) │ │ │ │ -3ADC9 GID Size 04 (4) │ │ │ │ -3ADCA GID 00000000 (0) │ │ │ │ -3ADCE PAYLOAD │ │ │ │ - │ │ │ │ -3C82E LOCAL HEADER #34 04034B50 (67324752) │ │ │ │ -3C832 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3C833 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3C834 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3C836 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3C838 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -3C83C CRC BA8C2020 (3129745440) │ │ │ │ -3C840 Compressed Size 000009A7 (2471) │ │ │ │ -3C844 Uncompressed Size 00001B65 (7013) │ │ │ │ -3C848 Filename Length 0010 (16) │ │ │ │ -3C84A Extra Length 001C (28) │ │ │ │ -3C84C Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3C84C: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3C85C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3C85E Length 0009 (9) │ │ │ │ -3C860 Flags 03 (3) 'Modification Access' │ │ │ │ -3C861 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -3C865 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -3C869 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3C86B Length 000B (11) │ │ │ │ -3C86D Version 01 (1) │ │ │ │ -3C86E UID Size 04 (4) │ │ │ │ -3C86F UID 00000000 (0) │ │ │ │ -3C873 GID Size 04 (4) │ │ │ │ -3C874 GID 00000000 (0) │ │ │ │ -3C878 PAYLOAD │ │ │ │ - │ │ │ │ -3D21F LOCAL HEADER #35 04034B50 (67324752) │ │ │ │ -3D223 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3D224 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3D225 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3D227 Compression Method 0008 (8) 'Deflated' │ │ │ │ -3D229 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -3D22D CRC 61869F50 (1636212560) │ │ │ │ -3D231 Compressed Size 000006B9 (1721) │ │ │ │ -3D235 Uncompressed Size 00001566 (5478) │ │ │ │ -3D239 Filename Length 0012 (18) │ │ │ │ -3D23B Extra Length 001C (28) │ │ │ │ -3D23D Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3D23D: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3D24F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3D251 Length 0009 (9) │ │ │ │ -3D253 Flags 03 (3) 'Modification Access' │ │ │ │ -3D254 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -3D258 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -3D25C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3D25E Length 000B (11) │ │ │ │ -3D260 Version 01 (1) │ │ │ │ -3D261 UID Size 04 (4) │ │ │ │ -3D262 UID 00000000 (0) │ │ │ │ -3D266 GID Size 04 (4) │ │ │ │ -3D267 GID 00000000 (0) │ │ │ │ -3D26B PAYLOAD │ │ │ │ - │ │ │ │ -3D924 LOCAL HEADER #36 04034B50 (67324752) │ │ │ │ -3D928 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -3D929 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -3D92A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -3D92C Compression Method 0008 (8) 'Deflated' │ │ │ │ -3D92E Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -3D932 CRC 1EE70229 (518455849) │ │ │ │ -3D936 Compressed Size 00002A16 (10774) │ │ │ │ -3D93A Uncompressed Size 0000B1DD (45533) │ │ │ │ -3D93E Filename Length 0010 (16) │ │ │ │ -3D940 Extra Length 001C (28) │ │ │ │ -3D942 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x3D942: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -3D952 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -3D954 Length 0009 (9) │ │ │ │ -3D956 Flags 03 (3) 'Modification Access' │ │ │ │ -3D957 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -3D95B Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -3D95F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -3D961 Length 000B (11) │ │ │ │ -3D963 Version 01 (1) │ │ │ │ -3D964 UID Size 04 (4) │ │ │ │ -3D965 UID 00000000 (0) │ │ │ │ -3D969 GID Size 04 (4) │ │ │ │ -3D96A GID 00000000 (0) │ │ │ │ -3D96E PAYLOAD │ │ │ │ - │ │ │ │ -40384 LOCAL HEADER #37 04034B50 (67324752) │ │ │ │ -40388 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -40389 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4038A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4038C Compression Method 0008 (8) 'Deflated' │ │ │ │ -4038E Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -40392 CRC A48C5994 (2760661396) │ │ │ │ -40396 Compressed Size 00001E8A (7818) │ │ │ │ -4039A Uncompressed Size 00009940 (39232) │ │ │ │ -4039E Filename Length 0012 (18) │ │ │ │ -403A0 Extra Length 001C (28) │ │ │ │ -403A2 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x403A2: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -403B4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -403B6 Length 0009 (9) │ │ │ │ -403B8 Flags 03 (3) 'Modification Access' │ │ │ │ -403B9 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -403BD Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -403C1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -403C3 Length 000B (11) │ │ │ │ -403C5 Version 01 (1) │ │ │ │ -403C6 UID Size 04 (4) │ │ │ │ -403C7 UID 00000000 (0) │ │ │ │ -403CB GID Size 04 (4) │ │ │ │ -403CC GID 00000000 (0) │ │ │ │ -403D0 PAYLOAD │ │ │ │ - │ │ │ │ -4225A LOCAL HEADER #38 04034B50 (67324752) │ │ │ │ -4225E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4225F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -42260 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -42262 Compression Method 0008 (8) 'Deflated' │ │ │ │ -42264 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -42268 CRC 3DD39FE9 (1037279209) │ │ │ │ -4226C Compressed Size 0000144E (5198) │ │ │ │ -42270 Uncompressed Size 00007A46 (31302) │ │ │ │ -42274 Filename Length 0018 (24) │ │ │ │ -42276 Extra Length 001C (28) │ │ │ │ -42278 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x42278: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -42290 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -42292 Length 0009 (9) │ │ │ │ -42294 Flags 03 (3) 'Modification Access' │ │ │ │ -42295 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -42299 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -4229D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -4229F Length 000B (11) │ │ │ │ -422A1 Version 01 (1) │ │ │ │ -422A2 UID Size 04 (4) │ │ │ │ -422A3 UID 00000000 (0) │ │ │ │ -422A7 GID Size 04 (4) │ │ │ │ -422A8 GID 00000000 (0) │ │ │ │ -422AC PAYLOAD │ │ │ │ - │ │ │ │ -436FA LOCAL HEADER #39 04034B50 (67324752) │ │ │ │ -436FE Extract Zip Spec 14 (20) '2.0' │ │ │ │ -436FF Extract OS 00 (0) 'MS-DOS' │ │ │ │ -43700 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -43702 Compression Method 0008 (8) 'Deflated' │ │ │ │ -43704 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -43708 CRC FD9E19CB (4254996939) │ │ │ │ -4370C Compressed Size 000018D4 (6356) │ │ │ │ -43710 Uncompressed Size 0000A83A (43066) │ │ │ │ -43714 Filename Length 001F (31) │ │ │ │ -43716 Extra Length 001C (28) │ │ │ │ -43718 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x43718: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -43737 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -43739 Length 0009 (9) │ │ │ │ -4373B Flags 03 (3) 'Modification Access' │ │ │ │ -4373C Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -43740 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -43744 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -43746 Length 000B (11) │ │ │ │ -43748 Version 01 (1) │ │ │ │ -43749 UID Size 04 (4) │ │ │ │ -4374A UID 00000000 (0) │ │ │ │ -4374E GID Size 04 (4) │ │ │ │ -4374F GID 00000000 (0) │ │ │ │ -43753 PAYLOAD │ │ │ │ - │ │ │ │ -45027 LOCAL HEADER #40 04034B50 (67324752) │ │ │ │ -4502B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4502C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4502D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4502F Compression Method 0008 (8) 'Deflated' │ │ │ │ -45031 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -45035 CRC 8E4EBF9E (2387525534) │ │ │ │ -45039 Compressed Size 000003F8 (1016) │ │ │ │ -4503D Uncompressed Size 000008A4 (2212) │ │ │ │ -45041 Filename Length 001E (30) │ │ │ │ -45043 Extra Length 001C (28) │ │ │ │ -45045 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x45045: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -45063 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -45065 Length 0009 (9) │ │ │ │ -45067 Flags 03 (3) 'Modification Access' │ │ │ │ -45068 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -4506C Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -45070 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -45072 Length 000B (11) │ │ │ │ -45074 Version 01 (1) │ │ │ │ -45075 UID Size 04 (4) │ │ │ │ -45076 UID 00000000 (0) │ │ │ │ -4507A GID Size 04 (4) │ │ │ │ -4507B GID 00000000 (0) │ │ │ │ -4507F PAYLOAD │ │ │ │ - │ │ │ │ -45477 LOCAL HEADER #41 04034B50 (67324752) │ │ │ │ -4547B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4547C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4547D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4547F Compression Method 0008 (8) 'Deflated' │ │ │ │ -45481 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -45485 CRC D9FD2FF3 (3657248755) │ │ │ │ -45489 Compressed Size 00004296 (17046) │ │ │ │ -4548D Uncompressed Size 0000D8E8 (55528) │ │ │ │ -45491 Filename Length 0013 (19) │ │ │ │ -45493 Extra Length 001C (28) │ │ │ │ -45495 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x45495: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -454A8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -454AA Length 0009 (9) │ │ │ │ -454AC Flags 03 (3) 'Modification Access' │ │ │ │ -454AD Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -454B1 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -454B5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -454B7 Length 000B (11) │ │ │ │ -454B9 Version 01 (1) │ │ │ │ -454BA UID Size 04 (4) │ │ │ │ -454BB UID 00000000 (0) │ │ │ │ -454BF GID Size 04 (4) │ │ │ │ -454C0 GID 00000000 (0) │ │ │ │ -454C4 PAYLOAD │ │ │ │ - │ │ │ │ -4975A LOCAL HEADER #42 04034B50 (67324752) │ │ │ │ -4975E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4975F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -49760 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -49762 Compression Method 0008 (8) 'Deflated' │ │ │ │ -49764 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -49768 CRC 9236E128 (2453070120) │ │ │ │ -4976C Compressed Size 000026C5 (9925) │ │ │ │ -49770 Uncompressed Size 00006E46 (28230) │ │ │ │ -49774 Filename Length 0019 (25) │ │ │ │ -49776 Extra Length 001C (28) │ │ │ │ -49778 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x49778: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -49791 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -49793 Length 0009 (9) │ │ │ │ -49795 Flags 03 (3) 'Modification Access' │ │ │ │ -49796 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -4979A Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -4979E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -497A0 Length 000B (11) │ │ │ │ -497A2 Version 01 (1) │ │ │ │ -497A3 UID Size 04 (4) │ │ │ │ -497A4 UID 00000000 (0) │ │ │ │ -497A8 GID Size 04 (4) │ │ │ │ -497A9 GID 00000000 (0) │ │ │ │ -497AD PAYLOAD │ │ │ │ - │ │ │ │ -4BE72 LOCAL HEADER #43 04034B50 (67324752) │ │ │ │ -4BE76 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4BE77 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4BE78 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4BE7A Compression Method 0008 (8) 'Deflated' │ │ │ │ -4BE7C Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -4BE80 CRC A84F7DF4 (2823781876) │ │ │ │ -4BE84 Compressed Size 0000273B (10043) │ │ │ │ -4BE88 Uncompressed Size 00008B84 (35716) │ │ │ │ -4BE8C Filename Length 0019 (25) │ │ │ │ -4BE8E Extra Length 001C (28) │ │ │ │ -4BE90 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4BE90: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -4BEA9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -4BEAB Length 0009 (9) │ │ │ │ -4BEAD Flags 03 (3) 'Modification Access' │ │ │ │ -4BEAE Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -4BEB2 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -4BEB6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -4BEB8 Length 000B (11) │ │ │ │ -4BEBA Version 01 (1) │ │ │ │ -4BEBB UID Size 04 (4) │ │ │ │ -4BEBC UID 00000000 (0) │ │ │ │ -4BEC0 GID Size 04 (4) │ │ │ │ -4BEC1 GID 00000000 (0) │ │ │ │ -4BEC5 PAYLOAD │ │ │ │ - │ │ │ │ -4E600 LOCAL HEADER #44 04034B50 (67324752) │ │ │ │ -4E604 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4E605 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4E606 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4E608 Compression Method 0008 (8) 'Deflated' │ │ │ │ -4E60A Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -4E60E CRC 4C908684 (1284540036) │ │ │ │ -4E612 Compressed Size 00000CFC (3324) │ │ │ │ -4E616 Uncompressed Size 0000517B (20859) │ │ │ │ -4E61A Filename Length 0021 (33) │ │ │ │ -4E61C Extra Length 001C (28) │ │ │ │ -4E61E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4E61E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -4E63F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -4E641 Length 0009 (9) │ │ │ │ -4E643 Flags 03 (3) 'Modification Access' │ │ │ │ -4E644 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -4E648 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -4E64C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -4E64E Length 000B (11) │ │ │ │ -4E650 Version 01 (1) │ │ │ │ -4E651 UID Size 04 (4) │ │ │ │ -4E652 UID 00000000 (0) │ │ │ │ -4E656 GID Size 04 (4) │ │ │ │ -4E657 GID 00000000 (0) │ │ │ │ -4E65B PAYLOAD │ │ │ │ - │ │ │ │ -4F357 LOCAL HEADER #45 04034B50 (67324752) │ │ │ │ -4F35B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4F35C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4F35D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4F35F Compression Method 0008 (8) 'Deflated' │ │ │ │ -4F361 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -4F365 CRC 4527F9F0 (1160247792) │ │ │ │ -4F369 Compressed Size 00000469 (1129) │ │ │ │ -4F36D Uncompressed Size 00000932 (2354) │ │ │ │ -4F371 Filename Length 001B (27) │ │ │ │ -4F373 Extra Length 001C (28) │ │ │ │ -4F375 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4F375: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -4F390 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -4F392 Length 0009 (9) │ │ │ │ -4F394 Flags 03 (3) 'Modification Access' │ │ │ │ -4F395 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -4F399 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -4F39D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -4F39F Length 000B (11) │ │ │ │ -4F3A1 Version 01 (1) │ │ │ │ -4F3A2 UID Size 04 (4) │ │ │ │ -4F3A3 UID 00000000 (0) │ │ │ │ -4F3A7 GID Size 04 (4) │ │ │ │ -4F3A8 GID 00000000 (0) │ │ │ │ -4F3AC PAYLOAD │ │ │ │ - │ │ │ │ -4F815 LOCAL HEADER #46 04034B50 (67324752) │ │ │ │ -4F819 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -4F81A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -4F81B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -4F81D Compression Method 0008 (8) 'Deflated' │ │ │ │ -4F81F Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -4F823 CRC 18CB2039 (415965241) │ │ │ │ -4F827 Compressed Size 000016FF (5887) │ │ │ │ -4F82B Uncompressed Size 00007A12 (31250) │ │ │ │ -4F82F Filename Length 001F (31) │ │ │ │ -4F831 Extra Length 001C (28) │ │ │ │ -4F833 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x4F833: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -4F852 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -4F854 Length 0009 (9) │ │ │ │ -4F856 Flags 03 (3) 'Modification Access' │ │ │ │ -4F857 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -4F85B Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -4F85F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -4F861 Length 000B (11) │ │ │ │ -4F863 Version 01 (1) │ │ │ │ -4F864 UID Size 04 (4) │ │ │ │ -4F865 UID 00000000 (0) │ │ │ │ -4F869 GID Size 04 (4) │ │ │ │ -4F86A GID 00000000 (0) │ │ │ │ -4F86E PAYLOAD │ │ │ │ - │ │ │ │ -50F6D LOCAL HEADER #47 04034B50 (67324752) │ │ │ │ -50F71 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -50F72 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -50F73 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -50F75 Compression Method 0008 (8) 'Deflated' │ │ │ │ -50F77 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -50F7B CRC 6269335F (1651061599) │ │ │ │ -50F7F Compressed Size 00004160 (16736) │ │ │ │ -50F83 Uncompressed Size 0001D147 (119111) │ │ │ │ -50F87 Filename Length 0010 (16) │ │ │ │ -50F89 Extra Length 001C (28) │ │ │ │ -50F8B Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x50F8B: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -50F9B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -50F9D Length 0009 (9) │ │ │ │ -50F9F Flags 03 (3) 'Modification Access' │ │ │ │ -50FA0 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -50FA4 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -50FA8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -50FAA Length 000B (11) │ │ │ │ -50FAC Version 01 (1) │ │ │ │ -50FAD UID Size 04 (4) │ │ │ │ -50FAE UID 00000000 (0) │ │ │ │ -50FB2 GID Size 04 (4) │ │ │ │ -50FB3 GID 00000000 (0) │ │ │ │ -50FB7 PAYLOAD │ │ │ │ - │ │ │ │ -55117 LOCAL HEADER #48 04034B50 (67324752) │ │ │ │ -5511B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -5511C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -5511D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -5511F Compression Method 0008 (8) 'Deflated' │ │ │ │ -55121 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -55125 CRC 1C486A07 (474507783) │ │ │ │ -55129 Compressed Size 00000A98 (2712) │ │ │ │ -5512D Uncompressed Size 00002106 (8454) │ │ │ │ -55131 Filename Length 0014 (20) │ │ │ │ -55133 Extra Length 001C (28) │ │ │ │ -55135 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x55135: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -55149 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -5514B Length 0009 (9) │ │ │ │ -5514D Flags 03 (3) 'Modification Access' │ │ │ │ -5514E Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -55152 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -55156 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -55158 Length 000B (11) │ │ │ │ -5515A Version 01 (1) │ │ │ │ -5515B UID Size 04 (4) │ │ │ │ -5515C UID 00000000 (0) │ │ │ │ -55160 GID Size 04 (4) │ │ │ │ -55161 GID 00000000 (0) │ │ │ │ -55165 PAYLOAD │ │ │ │ - │ │ │ │ -55BFD LOCAL HEADER #49 04034B50 (67324752) │ │ │ │ -55C01 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -55C02 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -55C03 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -55C05 Compression Method 0008 (8) 'Deflated' │ │ │ │ -55C07 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -55C0B CRC 94315B0D (2486262541) │ │ │ │ -55C0F Compressed Size 0000AC13 (44051) │ │ │ │ -55C13 Uncompressed Size 0003E93F (256319) │ │ │ │ -55C17 Filename Length 0017 (23) │ │ │ │ -55C19 Extra Length 001C (28) │ │ │ │ -55C1B Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x55C1B: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -55C32 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -55C34 Length 0009 (9) │ │ │ │ -55C36 Flags 03 (3) 'Modification Access' │ │ │ │ -55C37 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -55C3B Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -55C3F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -55C41 Length 000B (11) │ │ │ │ -55C43 Version 01 (1) │ │ │ │ -55C44 UID Size 04 (4) │ │ │ │ -55C45 UID 00000000 (0) │ │ │ │ -55C49 GID Size 04 (4) │ │ │ │ -55C4A GID 00000000 (0) │ │ │ │ -55C4E PAYLOAD │ │ │ │ - │ │ │ │ -60861 LOCAL HEADER #50 04034B50 (67324752) │ │ │ │ -60865 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -60866 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -60867 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -60869 Compression Method 0008 (8) 'Deflated' │ │ │ │ -6086B Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -6086F CRC EA7F3AE6 (3934206694) │ │ │ │ -60873 Compressed Size 00000463 (1123) │ │ │ │ -60877 Uncompressed Size 00000DF4 (3572) │ │ │ │ -6087B Filename Length 0013 (19) │ │ │ │ -6087D Extra Length 001C (28) │ │ │ │ -6087F Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6087F: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -60892 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -60894 Length 0009 (9) │ │ │ │ -60896 Flags 03 (3) 'Modification Access' │ │ │ │ -60897 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -6089B Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -6089F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -608A1 Length 000B (11) │ │ │ │ -608A3 Version 01 (1) │ │ │ │ -608A4 UID Size 04 (4) │ │ │ │ -608A5 UID 00000000 (0) │ │ │ │ -608A9 GID Size 04 (4) │ │ │ │ -608AA GID 00000000 (0) │ │ │ │ -608AE PAYLOAD │ │ │ │ - │ │ │ │ -60D11 LOCAL HEADER #51 04034B50 (67324752) │ │ │ │ -60D15 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -60D16 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -60D17 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -60D19 Compression Method 0008 (8) 'Deflated' │ │ │ │ -60D1B Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -60D1F CRC 8E059064 (2382729316) │ │ │ │ -60D23 Compressed Size 000014B4 (5300) │ │ │ │ -60D27 Uncompressed Size 000067DB (26587) │ │ │ │ -60D2B Filename Length 0012 (18) │ │ │ │ -60D2D Extra Length 001C (28) │ │ │ │ -60D2F Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x60D2F: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -60D41 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -60D43 Length 0009 (9) │ │ │ │ -60D45 Flags 03 (3) 'Modification Access' │ │ │ │ -60D46 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -60D4A Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -60D4E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -60D50 Length 000B (11) │ │ │ │ -60D52 Version 01 (1) │ │ │ │ -60D53 UID Size 04 (4) │ │ │ │ -60D54 UID 00000000 (0) │ │ │ │ -60D58 GID Size 04 (4) │ │ │ │ -60D59 GID 00000000 (0) │ │ │ │ -60D5D PAYLOAD │ │ │ │ - │ │ │ │ -62211 LOCAL HEADER #52 04034B50 (67324752) │ │ │ │ -62215 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -62216 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -62217 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -62219 Compression Method 0008 (8) 'Deflated' │ │ │ │ -6221B Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -6221F CRC 5454A1EE (1414832622) │ │ │ │ -62223 Compressed Size 000011F0 (4592) │ │ │ │ -62227 Uncompressed Size 0000410D (16653) │ │ │ │ -6222B Filename Length 0012 (18) │ │ │ │ -6222D Extra Length 001C (28) │ │ │ │ -6222F Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6222F: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -62241 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -62243 Length 0009 (9) │ │ │ │ -62245 Flags 03 (3) 'Modification Access' │ │ │ │ -62246 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -6224A Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -6224E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -62250 Length 000B (11) │ │ │ │ -62252 Version 01 (1) │ │ │ │ -62253 UID Size 04 (4) │ │ │ │ -62254 UID 00000000 (0) │ │ │ │ -62258 GID Size 04 (4) │ │ │ │ -62259 GID 00000000 (0) │ │ │ │ -6225D PAYLOAD │ │ │ │ - │ │ │ │ -6344D LOCAL HEADER #53 04034B50 (67324752) │ │ │ │ -63451 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -63452 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -63453 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -63455 Compression Method 0008 (8) 'Deflated' │ │ │ │ -63457 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -6345B CRC C8A917EC (3366524908) │ │ │ │ -6345F Compressed Size 000009DB (2523) │ │ │ │ -63463 Uncompressed Size 0000352A (13610) │ │ │ │ -63467 Filename Length 0019 (25) │ │ │ │ -63469 Extra Length 001C (28) │ │ │ │ -6346B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6346B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -63484 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -63486 Length 0009 (9) │ │ │ │ -63488 Flags 03 (3) 'Modification Access' │ │ │ │ -63489 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -6348D Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -63491 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -63493 Length 000B (11) │ │ │ │ -63495 Version 01 (1) │ │ │ │ -63496 UID Size 04 (4) │ │ │ │ -63497 UID 00000000 (0) │ │ │ │ -6349B GID Size 04 (4) │ │ │ │ -6349C GID 00000000 (0) │ │ │ │ -634A0 PAYLOAD │ │ │ │ - │ │ │ │ -63E7B LOCAL HEADER #54 04034B50 (67324752) │ │ │ │ -63E7F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -63E80 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -63E81 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -63E83 Compression Method 0008 (8) 'Deflated' │ │ │ │ -63E85 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -63E89 CRC 047C3EB7 (75251383) │ │ │ │ -63E8D Compressed Size 00002039 (8249) │ │ │ │ -63E91 Uncompressed Size 00010919 (67865) │ │ │ │ -63E95 Filename Length 0019 (25) │ │ │ │ -63E97 Extra Length 001C (28) │ │ │ │ -63E99 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x63E99: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -63EB2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -63EB4 Length 0009 (9) │ │ │ │ -63EB6 Flags 03 (3) 'Modification Access' │ │ │ │ -63EB7 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -63EBB Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -63EBF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -63EC1 Length 000B (11) │ │ │ │ -63EC3 Version 01 (1) │ │ │ │ -63EC4 UID Size 04 (4) │ │ │ │ -63EC5 UID 00000000 (0) │ │ │ │ -63EC9 GID Size 04 (4) │ │ │ │ -63ECA GID 00000000 (0) │ │ │ │ -63ECE PAYLOAD │ │ │ │ - │ │ │ │ -65F07 LOCAL HEADER #55 04034B50 (67324752) │ │ │ │ -65F0B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -65F0C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -65F0D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -65F0F Compression Method 0008 (8) 'Deflated' │ │ │ │ -65F11 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -65F15 CRC D0372DB1 (3493277105) │ │ │ │ -65F19 Compressed Size 0000177F (6015) │ │ │ │ -65F1D Uncompressed Size 0000472D (18221) │ │ │ │ -65F21 Filename Length 0014 (20) │ │ │ │ -65F23 Extra Length 001C (28) │ │ │ │ -65F25 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x65F25: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -65F39 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -65F3B Length 0009 (9) │ │ │ │ -65F3D Flags 03 (3) 'Modification Access' │ │ │ │ -65F3E Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -65F42 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -65F46 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -65F48 Length 000B (11) │ │ │ │ -65F4A Version 01 (1) │ │ │ │ -65F4B UID Size 04 (4) │ │ │ │ -65F4C UID 00000000 (0) │ │ │ │ -65F50 GID Size 04 (4) │ │ │ │ -65F51 GID 00000000 (0) │ │ │ │ -65F55 PAYLOAD │ │ │ │ - │ │ │ │ -676D4 LOCAL HEADER #56 04034B50 (67324752) │ │ │ │ -676D8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -676D9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -676DA General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -676DC Compression Method 0008 (8) 'Deflated' │ │ │ │ -676DE Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -676E2 CRC FAE30038 (4209180728) │ │ │ │ -676E6 Compressed Size 0000040B (1035) │ │ │ │ -676EA Uncompressed Size 00000826 (2086) │ │ │ │ -676EE Filename Length 001C (28) │ │ │ │ -676F0 Extra Length 001C (28) │ │ │ │ -676F2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x676F2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6770E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -67710 Length 0009 (9) │ │ │ │ -67712 Flags 03 (3) 'Modification Access' │ │ │ │ -67713 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -67717 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -6771B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6771D Length 000B (11) │ │ │ │ -6771F Version 01 (1) │ │ │ │ -67720 UID Size 04 (4) │ │ │ │ -67721 UID 00000000 (0) │ │ │ │ -67725 GID Size 04 (4) │ │ │ │ -67726 GID 00000000 (0) │ │ │ │ -6772A PAYLOAD │ │ │ │ - │ │ │ │ -67B35 LOCAL HEADER #57 04034B50 (67324752) │ │ │ │ -67B39 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -67B3A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -67B3B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -67B3D Compression Method 0008 (8) 'Deflated' │ │ │ │ -67B3F Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -67B43 CRC 60A198F7 (1621203191) │ │ │ │ -67B47 Compressed Size 000024AE (9390) │ │ │ │ -67B4B Uncompressed Size 0000B5F9 (46585) │ │ │ │ -67B4F Filename Length 001F (31) │ │ │ │ -67B51 Extra Length 001C (28) │ │ │ │ -67B53 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x67B53: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -67B72 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -67B74 Length 0009 (9) │ │ │ │ -67B76 Flags 03 (3) 'Modification Access' │ │ │ │ -67B77 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -67B7B Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -67B7F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -67B81 Length 000B (11) │ │ │ │ -67B83 Version 01 (1) │ │ │ │ -67B84 UID Size 04 (4) │ │ │ │ -67B85 UID 00000000 (0) │ │ │ │ -67B89 GID Size 04 (4) │ │ │ │ -67B8A GID 00000000 (0) │ │ │ │ -67B8E PAYLOAD │ │ │ │ - │ │ │ │ -6A03C LOCAL HEADER #58 04034B50 (67324752) │ │ │ │ -6A040 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6A041 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6A042 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6A044 Compression Method 0008 (8) 'Deflated' │ │ │ │ -6A046 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -6A04A CRC 1B69433B (459883323) │ │ │ │ -6A04E Compressed Size 00000E5E (3678) │ │ │ │ -6A052 Uncompressed Size 0000527E (21118) │ │ │ │ -6A056 Filename Length 001F (31) │ │ │ │ -6A058 Extra Length 001C (28) │ │ │ │ -6A05A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6A05A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6A079 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6A07B Length 0009 (9) │ │ │ │ -6A07D Flags 03 (3) 'Modification Access' │ │ │ │ -6A07E Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -6A082 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -6A086 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6A088 Length 000B (11) │ │ │ │ -6A08A Version 01 (1) │ │ │ │ -6A08B UID Size 04 (4) │ │ │ │ -6A08C UID 00000000 (0) │ │ │ │ -6A090 GID Size 04 (4) │ │ │ │ -6A091 GID 00000000 (0) │ │ │ │ -6A095 PAYLOAD │ │ │ │ - │ │ │ │ -6AEF3 LOCAL HEADER #59 04034B50 (67324752) │ │ │ │ -6AEF7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6AEF8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6AEF9 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6AEFB Compression Method 0008 (8) 'Deflated' │ │ │ │ -6AEFD Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -6AF01 CRC D6CA7B61 (3603594081) │ │ │ │ -6AF05 Compressed Size 00000A44 (2628) │ │ │ │ -6AF09 Uncompressed Size 0000244F (9295) │ │ │ │ -6AF0D Filename Length 0013 (19) │ │ │ │ -6AF0F Extra Length 001C (28) │ │ │ │ -6AF11 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6AF11: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6AF24 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6AF26 Length 0009 (9) │ │ │ │ -6AF28 Flags 03 (3) 'Modification Access' │ │ │ │ -6AF29 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -6AF2D Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -6AF31 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6AF33 Length 000B (11) │ │ │ │ -6AF35 Version 01 (1) │ │ │ │ -6AF36 UID Size 04 (4) │ │ │ │ -6AF37 UID 00000000 (0) │ │ │ │ -6AF3B GID Size 04 (4) │ │ │ │ -6AF3C GID 00000000 (0) │ │ │ │ -6AF40 PAYLOAD │ │ │ │ - │ │ │ │ -6B984 LOCAL HEADER #60 04034B50 (67324752) │ │ │ │ -6B988 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6B989 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6B98A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6B98C Compression Method 0008 (8) 'Deflated' │ │ │ │ -6B98E Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -6B992 CRC 89786723 (2306369315) │ │ │ │ -6B996 Compressed Size 0000248C (9356) │ │ │ │ -6B99A Uncompressed Size 0000B84D (47181) │ │ │ │ -6B99E Filename Length 0019 (25) │ │ │ │ -6B9A0 Extra Length 001C (28) │ │ │ │ -6B9A2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6B9A2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6B9BB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6B9BD Length 0009 (9) │ │ │ │ -6B9BF Flags 03 (3) 'Modification Access' │ │ │ │ -6B9C0 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -6B9C4 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -6B9C8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6B9CA Length 000B (11) │ │ │ │ -6B9CC Version 01 (1) │ │ │ │ -6B9CD UID Size 04 (4) │ │ │ │ -6B9CE UID 00000000 (0) │ │ │ │ -6B9D2 GID Size 04 (4) │ │ │ │ -6B9D3 GID 00000000 (0) │ │ │ │ -6B9D7 PAYLOAD │ │ │ │ - │ │ │ │ -6DE63 LOCAL HEADER #61 04034B50 (67324752) │ │ │ │ -6DE67 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6DE68 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6DE69 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6DE6B Compression Method 0008 (8) 'Deflated' │ │ │ │ -6DE6D Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -6DE71 CRC 0F5745D7 (257377751) │ │ │ │ -6DE75 Compressed Size 00000EFA (3834) │ │ │ │ -6DE79 Uncompressed Size 00003A2D (14893) │ │ │ │ -6DE7D Filename Length 0024 (36) │ │ │ │ -6DE7F Extra Length 001C (28) │ │ │ │ -6DE81 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6DE81: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6DEA5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6DEA7 Length 0009 (9) │ │ │ │ -6DEA9 Flags 03 (3) 'Modification Access' │ │ │ │ -6DEAA Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -6DEAE Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -6DEB2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6DEB4 Length 000B (11) │ │ │ │ -6DEB6 Version 01 (1) │ │ │ │ -6DEB7 UID Size 04 (4) │ │ │ │ -6DEB8 UID 00000000 (0) │ │ │ │ -6DEBC GID Size 04 (4) │ │ │ │ -6DEBD GID 00000000 (0) │ │ │ │ -6DEC1 PAYLOAD │ │ │ │ - │ │ │ │ -6EDBB LOCAL HEADER #62 04034B50 (67324752) │ │ │ │ -6EDBF Extract Zip Spec 14 (20) '2.0' │ │ │ │ -6EDC0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -6EDC1 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -6EDC3 Compression Method 0008 (8) 'Deflated' │ │ │ │ -6EDC5 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -6EDC9 CRC E4C6B7A9 (3838228393) │ │ │ │ -6EDCD Compressed Size 00001ABD (6845) │ │ │ │ -6EDD1 Uncompressed Size 00005F39 (24377) │ │ │ │ -6EDD5 Filename Length 0017 (23) │ │ │ │ -6EDD7 Extra Length 001C (28) │ │ │ │ -6EDD9 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x6EDD9: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -6EDF0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -6EDF2 Length 0009 (9) │ │ │ │ -6EDF4 Flags 03 (3) 'Modification Access' │ │ │ │ -6EDF5 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -6EDF9 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -6EDFD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -6EDFF Length 000B (11) │ │ │ │ -6EE01 Version 01 (1) │ │ │ │ -6EE02 UID Size 04 (4) │ │ │ │ -6EE03 UID 00000000 (0) │ │ │ │ -6EE07 GID Size 04 (4) │ │ │ │ -6EE08 GID 00000000 (0) │ │ │ │ -6EE0C PAYLOAD │ │ │ │ - │ │ │ │ -708C9 LOCAL HEADER #63 04034B50 (67324752) │ │ │ │ -708CD Extract Zip Spec 14 (20) '2.0' │ │ │ │ -708CE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -708CF General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -708D1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -708D3 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -708D7 CRC 3DD8544C (1037587532) │ │ │ │ -708DB Compressed Size 00000ED1 (3793) │ │ │ │ -708DF Uncompressed Size 000038DE (14558) │ │ │ │ -708E3 Filename Length 0023 (35) │ │ │ │ -708E5 Extra Length 001C (28) │ │ │ │ -708E7 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x708E7: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -7090A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -7090C Length 0009 (9) │ │ │ │ -7090E Flags 03 (3) 'Modification Access' │ │ │ │ -7090F Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -70913 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -70917 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -70919 Length 000B (11) │ │ │ │ -7091B Version 01 (1) │ │ │ │ -7091C UID Size 04 (4) │ │ │ │ -7091D UID 00000000 (0) │ │ │ │ -70921 GID Size 04 (4) │ │ │ │ -70922 GID 00000000 (0) │ │ │ │ -70926 PAYLOAD │ │ │ │ - │ │ │ │ -717F7 LOCAL HEADER #64 04034B50 (67324752) │ │ │ │ -717FB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -717FC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -717FD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -717FF Compression Method 0008 (8) 'Deflated' │ │ │ │ -71801 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -71805 CRC 2DB7929F (767005343) │ │ │ │ -71809 Compressed Size 00000113 (275) │ │ │ │ -7180D Uncompressed Size 000001F3 (499) │ │ │ │ -71811 Filename Length 001B (27) │ │ │ │ -71813 Extra Length 001C (28) │ │ │ │ -71815 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x71815: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -71830 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -71832 Length 0009 (9) │ │ │ │ -71834 Flags 03 (3) 'Modification Access' │ │ │ │ -71835 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -71839 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -7183D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -7183F Length 000B (11) │ │ │ │ -71841 Version 01 (1) │ │ │ │ -71842 UID Size 04 (4) │ │ │ │ -71843 UID 00000000 (0) │ │ │ │ -71847 GID Size 04 (4) │ │ │ │ -71848 GID 00000000 (0) │ │ │ │ -7184C PAYLOAD │ │ │ │ - │ │ │ │ -7195F LOCAL HEADER #65 04034B50 (67324752) │ │ │ │ -71963 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -71964 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -71965 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -71967 Compression Method 0008 (8) 'Deflated' │ │ │ │ -71969 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -7196D CRC 087EAA20 (142518816) │ │ │ │ -71971 Compressed Size 0000189E (6302) │ │ │ │ -71975 Uncompressed Size 00008F47 (36679) │ │ │ │ -71979 Filename Length 001D (29) │ │ │ │ -7197B Extra Length 001C (28) │ │ │ │ -7197D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x7197D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -7199A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -7199C Length 0009 (9) │ │ │ │ -7199E Flags 03 (3) 'Modification Access' │ │ │ │ -7199F Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -719A3 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -719A7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -719A9 Length 000B (11) │ │ │ │ -719AB Version 01 (1) │ │ │ │ -719AC UID Size 04 (4) │ │ │ │ -719AD UID 00000000 (0) │ │ │ │ -719B1 GID Size 04 (4) │ │ │ │ -719B2 GID 00000000 (0) │ │ │ │ -719B6 PAYLOAD │ │ │ │ - │ │ │ │ -73254 LOCAL HEADER #66 04034B50 (67324752) │ │ │ │ -73258 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -73259 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -7325A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -7325C Compression Method 0008 (8) 'Deflated' │ │ │ │ -7325E Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -73262 CRC 516C0F5D (1366036317) │ │ │ │ -73266 Compressed Size 0000164A (5706) │ │ │ │ -7326A Uncompressed Size 00003A9C (15004) │ │ │ │ -7326E Filename Length 0015 (21) │ │ │ │ -73270 Extra Length 001C (28) │ │ │ │ -73272 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x73272: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -73287 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -73289 Length 0009 (9) │ │ │ │ -7328B Flags 03 (3) 'Modification Access' │ │ │ │ -7328C Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -73290 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -73294 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -73296 Length 000B (11) │ │ │ │ -73298 Version 01 (1) │ │ │ │ -73299 UID Size 04 (4) │ │ │ │ -7329A UID 00000000 (0) │ │ │ │ -7329E GID Size 04 (4) │ │ │ │ -7329F GID 00000000 (0) │ │ │ │ -732A3 PAYLOAD │ │ │ │ - │ │ │ │ -748ED LOCAL HEADER #67 04034B50 (67324752) │ │ │ │ -748F1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -748F2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -748F3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -748F5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -748F7 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -748FB CRC 795D0182 (2036138370) │ │ │ │ -748FF Compressed Size 00003B42 (15170) │ │ │ │ -74903 Uncompressed Size 00011C7F (72831) │ │ │ │ -74907 Filename Length 0016 (22) │ │ │ │ -74909 Extra Length 001C (28) │ │ │ │ -7490B Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x7490B: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -74921 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -74923 Length 0009 (9) │ │ │ │ -74925 Flags 03 (3) 'Modification Access' │ │ │ │ -74926 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -7492A Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -7492E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -74930 Length 000B (11) │ │ │ │ -74932 Version 01 (1) │ │ │ │ -74933 UID Size 04 (4) │ │ │ │ -74934 UID 00000000 (0) │ │ │ │ -74938 GID Size 04 (4) │ │ │ │ -74939 GID 00000000 (0) │ │ │ │ -7493D PAYLOAD │ │ │ │ - │ │ │ │ -7847F LOCAL HEADER #68 04034B50 (67324752) │ │ │ │ -78483 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -78484 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -78485 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -78487 Compression Method 0008 (8) 'Deflated' │ │ │ │ -78489 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -7848D CRC EE0EAC4F (3993939023) │ │ │ │ -78491 Compressed Size 00003EF5 (16117) │ │ │ │ -78495 Uncompressed Size 0001CBD3 (117715) │ │ │ │ -78499 Filename Length 0019 (25) │ │ │ │ -7849B Extra Length 001C (28) │ │ │ │ -7849D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x7849D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -784B6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -784B8 Length 0009 (9) │ │ │ │ -784BA Flags 03 (3) 'Modification Access' │ │ │ │ -784BB Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -784BF Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -784C3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -784C5 Length 000B (11) │ │ │ │ -784C7 Version 01 (1) │ │ │ │ -784C8 UID Size 04 (4) │ │ │ │ -784C9 UID 00000000 (0) │ │ │ │ -784CD GID Size 04 (4) │ │ │ │ -784CE GID 00000000 (0) │ │ │ │ -784D2 PAYLOAD │ │ │ │ - │ │ │ │ -7C3C7 LOCAL HEADER #69 04034B50 (67324752) │ │ │ │ -7C3CB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -7C3CC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -7C3CD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -7C3CF Compression Method 0008 (8) 'Deflated' │ │ │ │ -7C3D1 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -7C3D5 CRC 4FA725C6 (1336354246) │ │ │ │ -7C3D9 Compressed Size 00000835 (2101) │ │ │ │ -7C3DD Uncompressed Size 00003384 (13188) │ │ │ │ -7C3E1 Filename Length 0011 (17) │ │ │ │ -7C3E3 Extra Length 001C (28) │ │ │ │ -7C3E5 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x7C3E5: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -7C3F6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -7C3F8 Length 0009 (9) │ │ │ │ -7C3FA Flags 03 (3) 'Modification Access' │ │ │ │ -7C3FB Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -7C3FF Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -7C403 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -7C405 Length 000B (11) │ │ │ │ -7C407 Version 01 (1) │ │ │ │ -7C408 UID Size 04 (4) │ │ │ │ -7C409 UID 00000000 (0) │ │ │ │ -7C40D GID Size 04 (4) │ │ │ │ -7C40E GID 00000000 (0) │ │ │ │ -7C412 PAYLOAD │ │ │ │ - │ │ │ │ -7CC47 LOCAL HEADER #70 04034B50 (67324752) │ │ │ │ -7CC4B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -7CC4C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -7CC4D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -7CC4F Compression Method 0008 (8) 'Deflated' │ │ │ │ -7CC51 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -7CC55 CRC 7A11C564 (2047984996) │ │ │ │ -7CC59 Compressed Size 000051B9 (20921) │ │ │ │ -7CC5D Uncompressed Size 0001FBE0 (130016) │ │ │ │ -7CC61 Filename Length 0015 (21) │ │ │ │ -7CC63 Extra Length 001C (28) │ │ │ │ -7CC65 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x7CC65: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -7CC7A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -7CC7C Length 0009 (9) │ │ │ │ -7CC7E Flags 03 (3) 'Modification Access' │ │ │ │ -7CC7F Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -7CC83 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -7CC87 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -7CC89 Length 000B (11) │ │ │ │ -7CC8B Version 01 (1) │ │ │ │ -7CC8C UID Size 04 (4) │ │ │ │ -7CC8D UID 00000000 (0) │ │ │ │ -7CC91 GID Size 04 (4) │ │ │ │ -7CC92 GID 00000000 (0) │ │ │ │ -7CC96 PAYLOAD │ │ │ │ - │ │ │ │ -81E4F LOCAL HEADER #71 04034B50 (67324752) │ │ │ │ -81E53 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -81E54 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -81E55 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -81E57 Compression Method 0008 (8) 'Deflated' │ │ │ │ -81E59 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -81E5D CRC EBD2C2C9 (3956458185) │ │ │ │ -81E61 Compressed Size 00001B18 (6936) │ │ │ │ -81E65 Uncompressed Size 00008213 (33299) │ │ │ │ -81E69 Filename Length 0019 (25) │ │ │ │ -81E6B Extra Length 001C (28) │ │ │ │ -81E6D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x81E6D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -81E86 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -81E88 Length 0009 (9) │ │ │ │ -81E8A Flags 03 (3) 'Modification Access' │ │ │ │ -81E8B Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -81E8F Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -81E93 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -81E95 Length 000B (11) │ │ │ │ -81E97 Version 01 (1) │ │ │ │ -81E98 UID Size 04 (4) │ │ │ │ -81E99 UID 00000000 (0) │ │ │ │ -81E9D GID Size 04 (4) │ │ │ │ -81E9E GID 00000000 (0) │ │ │ │ -81EA2 PAYLOAD │ │ │ │ - │ │ │ │ -839BA LOCAL HEADER #72 04034B50 (67324752) │ │ │ │ -839BE Extract Zip Spec 14 (20) '2.0' │ │ │ │ -839BF Extract OS 00 (0) 'MS-DOS' │ │ │ │ -839C0 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -839C2 Compression Method 0008 (8) 'Deflated' │ │ │ │ -839C4 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -839C8 CRC 2EDD972E (786274094) │ │ │ │ -839CC Compressed Size 00000D97 (3479) │ │ │ │ -839D0 Uncompressed Size 00002EA0 (11936) │ │ │ │ -839D4 Filename Length 0018 (24) │ │ │ │ -839D6 Extra Length 001C (28) │ │ │ │ -839D8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x839D8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -839F0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -839F2 Length 0009 (9) │ │ │ │ -839F4 Flags 03 (3) 'Modification Access' │ │ │ │ -839F5 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -839F9 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -839FD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -839FF Length 000B (11) │ │ │ │ -83A01 Version 01 (1) │ │ │ │ -83A02 UID Size 04 (4) │ │ │ │ -83A03 UID 00000000 (0) │ │ │ │ -83A07 GID Size 04 (4) │ │ │ │ -83A08 GID 00000000 (0) │ │ │ │ -83A0C PAYLOAD │ │ │ │ - │ │ │ │ -847A3 LOCAL HEADER #73 04034B50 (67324752) │ │ │ │ -847A7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -847A8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -847A9 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -847AB Compression Method 0008 (8) 'Deflated' │ │ │ │ -847AD Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -847B1 CRC 037E3A9E (58604190) │ │ │ │ -847B5 Compressed Size 000001E1 (481) │ │ │ │ -847B9 Uncompressed Size 00000324 (804) │ │ │ │ -847BD Filename Length 0011 (17) │ │ │ │ -847BF Extra Length 001C (28) │ │ │ │ -847C1 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x847C1: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -847D2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -847D4 Length 0009 (9) │ │ │ │ -847D6 Flags 03 (3) 'Modification Access' │ │ │ │ -847D7 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -847DB Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -847DF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -847E1 Length 000B (11) │ │ │ │ -847E3 Version 01 (1) │ │ │ │ -847E4 UID Size 04 (4) │ │ │ │ -847E5 UID 00000000 (0) │ │ │ │ -847E9 GID Size 04 (4) │ │ │ │ -847EA GID 00000000 (0) │ │ │ │ -847EE PAYLOAD │ │ │ │ - │ │ │ │ -849CF LOCAL HEADER #74 04034B50 (67324752) │ │ │ │ -849D3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -849D4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -849D5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -849D7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -849D9 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -849DD CRC 55187787 (1427666823) │ │ │ │ -849E1 Compressed Size 000006C3 (1731) │ │ │ │ -849E5 Uncompressed Size 0000143A (5178) │ │ │ │ -849E9 Filename Length 0019 (25) │ │ │ │ -849EB Extra Length 001C (28) │ │ │ │ -849ED Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x849ED: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -84A06 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -84A08 Length 0009 (9) │ │ │ │ -84A0A Flags 03 (3) 'Modification Access' │ │ │ │ -84A0B Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -84A0F Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -84A13 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -84A15 Length 000B (11) │ │ │ │ -84A17 Version 01 (1) │ │ │ │ -84A18 UID Size 04 (4) │ │ │ │ -84A19 UID 00000000 (0) │ │ │ │ -84A1D GID Size 04 (4) │ │ │ │ -84A1E GID 00000000 (0) │ │ │ │ -84A22 PAYLOAD │ │ │ │ - │ │ │ │ -850E5 LOCAL HEADER #75 04034B50 (67324752) │ │ │ │ -850E9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -850EA Extract OS 00 (0) 'MS-DOS' │ │ │ │ -850EB General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -850ED Compression Method 0008 (8) 'Deflated' │ │ │ │ -850EF Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -850F3 CRC 2BFA1E54 (737812052) │ │ │ │ -850F7 Compressed Size 00001EA7 (7847) │ │ │ │ -850FB Uncompressed Size 0000CA55 (51797) │ │ │ │ -850FF Filename Length 0018 (24) │ │ │ │ -85101 Extra Length 001C (28) │ │ │ │ -85103 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x85103: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8511B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -8511D Length 0009 (9) │ │ │ │ -8511F Flags 03 (3) 'Modification Access' │ │ │ │ -85120 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -85124 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -85128 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8512A Length 000B (11) │ │ │ │ -8512C Version 01 (1) │ │ │ │ -8512D UID Size 04 (4) │ │ │ │ -8512E UID 00000000 (0) │ │ │ │ -85132 GID Size 04 (4) │ │ │ │ -85133 GID 00000000 (0) │ │ │ │ -85137 PAYLOAD │ │ │ │ - │ │ │ │ -86FDE LOCAL HEADER #76 04034B50 (67324752) │ │ │ │ -86FE2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -86FE3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -86FE4 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -86FE6 Compression Method 0008 (8) 'Deflated' │ │ │ │ -86FE8 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -86FEC CRC A14300A7 (2705522855) │ │ │ │ -86FF0 Compressed Size 00001C09 (7177) │ │ │ │ -86FF4 Uncompressed Size 0000C5BE (50622) │ │ │ │ -86FF8 Filename Length 0012 (18) │ │ │ │ -86FFA Extra Length 001C (28) │ │ │ │ -86FFC Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x86FFC: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8700E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -87010 Length 0009 (9) │ │ │ │ -87012 Flags 03 (3) 'Modification Access' │ │ │ │ -87013 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -87017 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -8701B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8701D Length 000B (11) │ │ │ │ -8701F Version 01 (1) │ │ │ │ -87020 UID Size 04 (4) │ │ │ │ -87021 UID 00000000 (0) │ │ │ │ -87025 GID Size 04 (4) │ │ │ │ -87026 GID 00000000 (0) │ │ │ │ -8702A PAYLOAD │ │ │ │ - │ │ │ │ -88C33 LOCAL HEADER #77 04034B50 (67324752) │ │ │ │ -88C37 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -88C38 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -88C39 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -88C3B Compression Method 0008 (8) 'Deflated' │ │ │ │ -88C3D Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -88C41 CRC EAB45C58 (3937688664) │ │ │ │ -88C45 Compressed Size 00001E17 (7703) │ │ │ │ -88C49 Uncompressed Size 000087D8 (34776) │ │ │ │ -88C4D Filename Length 0016 (22) │ │ │ │ -88C4F Extra Length 001C (28) │ │ │ │ -88C51 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x88C51: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -88C67 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -88C69 Length 0009 (9) │ │ │ │ -88C6B Flags 03 (3) 'Modification Access' │ │ │ │ -88C6C Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -88C70 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -88C74 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -88C76 Length 000B (11) │ │ │ │ -88C78 Version 01 (1) │ │ │ │ -88C79 UID Size 04 (4) │ │ │ │ -88C7A UID 00000000 (0) │ │ │ │ -88C7E GID Size 04 (4) │ │ │ │ -88C7F GID 00000000 (0) │ │ │ │ -88C83 PAYLOAD │ │ │ │ - │ │ │ │ -8AA9A LOCAL HEADER #78 04034B50 (67324752) │ │ │ │ -8AA9E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8AA9F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8AAA0 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8AAA2 Compression Method 0008 (8) 'Deflated' │ │ │ │ -8AAA4 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -8AAA8 CRC DF098402 (3741942786) │ │ │ │ -8AAAC Compressed Size 000029A9 (10665) │ │ │ │ -8AAB0 Uncompressed Size 0000D039 (53305) │ │ │ │ -8AAB4 Filename Length 001A (26) │ │ │ │ -8AAB6 Extra Length 001C (28) │ │ │ │ -8AAB8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8AAB8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8AAD2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -8AAD4 Length 0009 (9) │ │ │ │ -8AAD6 Flags 03 (3) 'Modification Access' │ │ │ │ -8AAD7 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -8AADB Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -8AADF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8AAE1 Length 000B (11) │ │ │ │ -8AAE3 Version 01 (1) │ │ │ │ -8AAE4 UID Size 04 (4) │ │ │ │ -8AAE5 UID 00000000 (0) │ │ │ │ -8AAE9 GID Size 04 (4) │ │ │ │ -8AAEA GID 00000000 (0) │ │ │ │ -8AAEE PAYLOAD │ │ │ │ - │ │ │ │ -8D497 LOCAL HEADER #79 04034B50 (67324752) │ │ │ │ -8D49B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8D49C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8D49D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8D49F Compression Method 0008 (8) 'Deflated' │ │ │ │ -8D4A1 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -8D4A5 CRC 843B749E (2218488990) │ │ │ │ -8D4A9 Compressed Size 000009AD (2477) │ │ │ │ -8D4AD Uncompressed Size 00001DB7 (7607) │ │ │ │ -8D4B1 Filename Length 0018 (24) │ │ │ │ -8D4B3 Extra Length 001C (28) │ │ │ │ -8D4B5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8D4B5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8D4CD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -8D4CF Length 0009 (9) │ │ │ │ -8D4D1 Flags 03 (3) 'Modification Access' │ │ │ │ -8D4D2 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -8D4D6 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -8D4DA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8D4DC Length 000B (11) │ │ │ │ -8D4DE Version 01 (1) │ │ │ │ -8D4DF UID Size 04 (4) │ │ │ │ -8D4E0 UID 00000000 (0) │ │ │ │ -8D4E4 GID Size 04 (4) │ │ │ │ -8D4E5 GID 00000000 (0) │ │ │ │ -8D4E9 PAYLOAD │ │ │ │ - │ │ │ │ -8DE96 LOCAL HEADER #80 04034B50 (67324752) │ │ │ │ -8DE9A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8DE9B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8DE9C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8DE9E Compression Method 0008 (8) 'Deflated' │ │ │ │ -8DEA0 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -8DEA4 CRC F5E2129F (4125233823) │ │ │ │ -8DEA8 Compressed Size 000016BC (5820) │ │ │ │ -8DEAC Uncompressed Size 000016CD (5837) │ │ │ │ -8DEB0 Filename Length 0015 (21) │ │ │ │ -8DEB2 Extra Length 001C (28) │ │ │ │ -8DEB4 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8DEB4: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8DEC9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -8DECB Length 0009 (9) │ │ │ │ -8DECD Flags 03 (3) 'Modification Access' │ │ │ │ -8DECE Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -8DED2 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -8DED6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8DED8 Length 000B (11) │ │ │ │ -8DEDA Version 01 (1) │ │ │ │ -8DEDB UID Size 04 (4) │ │ │ │ -8DEDC UID 00000000 (0) │ │ │ │ -8DEE0 GID Size 04 (4) │ │ │ │ -8DEE1 GID 00000000 (0) │ │ │ │ -8DEE5 PAYLOAD │ │ │ │ - │ │ │ │ -8F5A1 LOCAL HEADER #81 04034B50 (67324752) │ │ │ │ -8F5A5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -8F5A6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -8F5A7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -8F5A9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -8F5AB Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -8F5AF CRC F5E2129F (4125233823) │ │ │ │ -8F5B3 Compressed Size 000016BC (5820) │ │ │ │ -8F5B7 Uncompressed Size 000016CD (5837) │ │ │ │ -8F5BB Filename Length 001C (28) │ │ │ │ -8F5BD Extra Length 001C (28) │ │ │ │ -8F5BF Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x8F5BF: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -8F5DB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -8F5DD Length 0009 (9) │ │ │ │ -8F5DF Flags 03 (3) 'Modification Access' │ │ │ │ -8F5E0 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -8F5E4 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -8F5E8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -8F5EA Length 000B (11) │ │ │ │ -8F5EC Version 01 (1) │ │ │ │ -8F5ED UID Size 04 (4) │ │ │ │ -8F5EE UID 00000000 (0) │ │ │ │ -8F5F2 GID Size 04 (4) │ │ │ │ -8F5F3 GID 00000000 (0) │ │ │ │ -8F5F7 PAYLOAD │ │ │ │ - │ │ │ │ -90CB3 LOCAL HEADER #82 04034B50 (67324752) │ │ │ │ -90CB7 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -90CB8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -90CB9 General Purpose Flag 0000 (0) │ │ │ │ -90CBB Compression Method 0000 (0) 'Stored' │ │ │ │ -90CBD Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -90CC1 CRC FC95F24B (4237685323) │ │ │ │ -90CC5 Compressed Size 00001B84 (7044) │ │ │ │ -90CC9 Uncompressed Size 00001B84 (7044) │ │ │ │ -90CCD Filename Length 0016 (22) │ │ │ │ -90CCF Extra Length 001C (28) │ │ │ │ -90CD1 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x90CD1: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -90CE7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -90CE9 Length 0009 (9) │ │ │ │ -90CEB Flags 03 (3) 'Modification Access' │ │ │ │ -90CEC Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -90CF0 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -90CF4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -90CF6 Length 000B (11) │ │ │ │ -90CF8 Version 01 (1) │ │ │ │ -90CF9 UID Size 04 (4) │ │ │ │ -90CFA UID 00000000 (0) │ │ │ │ -90CFE GID Size 04 (4) │ │ │ │ -90CFF GID 00000000 (0) │ │ │ │ -90D03 PAYLOAD │ │ │ │ - │ │ │ │ -92887 LOCAL HEADER #83 04034B50 (67324752) │ │ │ │ -9288B Extract Zip Spec 0A (10) '1.0' │ │ │ │ -9288C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9288D General Purpose Flag 0000 (0) │ │ │ │ -9288F Compression Method 0000 (0) 'Stored' │ │ │ │ -92891 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -92895 CRC D0D71F86 (3503759238) │ │ │ │ -92899 Compressed Size 00000B7B (2939) │ │ │ │ -9289D Uncompressed Size 00000B7B (2939) │ │ │ │ -928A1 Filename Length 0016 (22) │ │ │ │ -928A3 Extra Length 001C (28) │ │ │ │ -928A5 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x928A5: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -928BB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -928BD Length 0009 (9) │ │ │ │ -928BF Flags 03 (3) 'Modification Access' │ │ │ │ -928C0 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -928C4 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -928C8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -928CA Length 000B (11) │ │ │ │ -928CC Version 01 (1) │ │ │ │ -928CD UID Size 04 (4) │ │ │ │ -928CE UID 00000000 (0) │ │ │ │ -928D2 GID Size 04 (4) │ │ │ │ -928D3 GID 00000000 (0) │ │ │ │ -928D7 PAYLOAD │ │ │ │ - │ │ │ │ -93452 LOCAL HEADER #84 04034B50 (67324752) │ │ │ │ -93456 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -93457 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -93458 General Purpose Flag 0000 (0) │ │ │ │ -9345A Compression Method 0000 (0) 'Stored' │ │ │ │ -9345C Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -93460 CRC FFF9C4D2 (4294558930) │ │ │ │ -93464 Compressed Size 0000138F (5007) │ │ │ │ -93468 Uncompressed Size 0000138F (5007) │ │ │ │ -9346C Filename Length 0016 (22) │ │ │ │ -9346E Extra Length 001C (28) │ │ │ │ -93470 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x93470: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -93486 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -93488 Length 0009 (9) │ │ │ │ -9348A Flags 03 (3) 'Modification Access' │ │ │ │ -9348B Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9348F Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -93493 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -93495 Length 000B (11) │ │ │ │ -93497 Version 01 (1) │ │ │ │ -93498 UID Size 04 (4) │ │ │ │ -93499 UID 00000000 (0) │ │ │ │ -9349D GID Size 04 (4) │ │ │ │ -9349E GID 00000000 (0) │ │ │ │ -934A2 PAYLOAD │ │ │ │ - │ │ │ │ -94831 LOCAL HEADER #85 04034B50 (67324752) │ │ │ │ -94835 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -94836 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -94837 General Purpose Flag 0000 (0) │ │ │ │ -94839 Compression Method 0000 (0) 'Stored' │ │ │ │ -9483B Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9483F CRC A1037E8E (2701360782) │ │ │ │ -94843 Compressed Size 0000145E (5214) │ │ │ │ -94847 Uncompressed Size 0000145E (5214) │ │ │ │ -9484B Filename Length 0016 (22) │ │ │ │ -9484D Extra Length 001C (28) │ │ │ │ -9484F Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9484F: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -94865 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -94867 Length 0009 (9) │ │ │ │ -94869 Flags 03 (3) 'Modification Access' │ │ │ │ -9486A Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9486E Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -94872 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -94874 Length 000B (11) │ │ │ │ -94876 Version 01 (1) │ │ │ │ -94877 UID Size 04 (4) │ │ │ │ -94878 UID 00000000 (0) │ │ │ │ -9487C GID Size 04 (4) │ │ │ │ -9487D GID 00000000 (0) │ │ │ │ -94881 PAYLOAD │ │ │ │ - │ │ │ │ -95CDF LOCAL HEADER #86 04034B50 (67324752) │ │ │ │ -95CE3 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -95CE4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -95CE5 General Purpose Flag 0000 (0) │ │ │ │ -95CE7 Compression Method 0000 (0) 'Stored' │ │ │ │ -95CE9 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -95CED CRC 5E9E64F1 (1587438833) │ │ │ │ -95CF1 Compressed Size 000008EC (2284) │ │ │ │ -95CF5 Uncompressed Size 000008EC (2284) │ │ │ │ -95CF9 Filename Length 0016 (22) │ │ │ │ -95CFB Extra Length 001C (28) │ │ │ │ -95CFD Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x95CFD: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -95D13 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -95D15 Length 0009 (9) │ │ │ │ -95D17 Flags 03 (3) 'Modification Access' │ │ │ │ -95D18 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -95D1C Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -95D20 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -95D22 Length 000B (11) │ │ │ │ -95D24 Version 01 (1) │ │ │ │ -95D25 UID Size 04 (4) │ │ │ │ -95D26 UID 00000000 (0) │ │ │ │ -95D2A GID Size 04 (4) │ │ │ │ -95D2B GID 00000000 (0) │ │ │ │ -95D2F PAYLOAD │ │ │ │ - │ │ │ │ -9661B LOCAL HEADER #87 04034B50 (67324752) │ │ │ │ -9661F Extract Zip Spec 0A (10) '1.0' │ │ │ │ -96620 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -96621 General Purpose Flag 0000 (0) │ │ │ │ -96623 Compression Method 0000 (0) 'Stored' │ │ │ │ -96625 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -96629 CRC 42E340AB (1122189483) │ │ │ │ -9662D Compressed Size 00001F2E (7982) │ │ │ │ -96631 Uncompressed Size 00001F2E (7982) │ │ │ │ -96635 Filename Length 001E (30) │ │ │ │ -96637 Extra Length 001C (28) │ │ │ │ -96639 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x96639: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -96657 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -96659 Length 0009 (9) │ │ │ │ -9665B Flags 03 (3) 'Modification Access' │ │ │ │ -9665C Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -96660 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -96664 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -96666 Length 000B (11) │ │ │ │ -96668 Version 01 (1) │ │ │ │ -96669 UID Size 04 (4) │ │ │ │ -9666A UID 00000000 (0) │ │ │ │ -9666E GID Size 04 (4) │ │ │ │ -9666F GID 00000000 (0) │ │ │ │ -96673 PAYLOAD │ │ │ │ - │ │ │ │ -985A1 LOCAL HEADER #88 04034B50 (67324752) │ │ │ │ -985A5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -985A6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -985A7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -985A9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -985AB Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -985AF CRC 5E60F644 (1583412804) │ │ │ │ -985B3 Compressed Size 000040A4 (16548) │ │ │ │ -985B7 Uncompressed Size 0001964C (104012) │ │ │ │ -985BB Filename Length 001A (26) │ │ │ │ -985BD Extra Length 001C (28) │ │ │ │ -985BF Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x985BF: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -985D9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -985DB Length 0009 (9) │ │ │ │ -985DD Flags 03 (3) 'Modification Access' │ │ │ │ -985DE Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -985E2 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -985E6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -985E8 Length 000B (11) │ │ │ │ -985EA Version 01 (1) │ │ │ │ -985EB UID Size 04 (4) │ │ │ │ -985EC UID 00000000 (0) │ │ │ │ -985F0 GID Size 04 (4) │ │ │ │ -985F1 GID 00000000 (0) │ │ │ │ -985F5 PAYLOAD │ │ │ │ - │ │ │ │ -9C699 LOCAL HEADER #89 04034B50 (67324752) │ │ │ │ -9C69D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9C69E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9C69F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9C6A1 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9C6A3 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9C6A7 CRC 20172B93 (538389395) │ │ │ │ -9C6AB Compressed Size 000029C9 (10697) │ │ │ │ -9C6AF Uncompressed Size 0000BAF5 (47861) │ │ │ │ -9C6B3 Filename Length 0018 (24) │ │ │ │ -9C6B5 Extra Length 001C (28) │ │ │ │ -9C6B7 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9C6B7: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9C6CF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9C6D1 Length 0009 (9) │ │ │ │ -9C6D3 Flags 03 (3) 'Modification Access' │ │ │ │ -9C6D4 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9C6D8 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9C6DC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9C6DE Length 000B (11) │ │ │ │ -9C6E0 Version 01 (1) │ │ │ │ -9C6E1 UID Size 04 (4) │ │ │ │ -9C6E2 UID 00000000 (0) │ │ │ │ -9C6E6 GID Size 04 (4) │ │ │ │ -9C6E7 GID 00000000 (0) │ │ │ │ -9C6EB PAYLOAD │ │ │ │ - │ │ │ │ -9F0B4 LOCAL HEADER #90 04034B50 (67324752) │ │ │ │ -9F0B8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F0B9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F0BA General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F0BC Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F0BE Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F0C2 CRC DCB3B516 (3702764822) │ │ │ │ -9F0C6 Compressed Size 000000AE (174) │ │ │ │ -9F0CA Uncompressed Size 000000FC (252) │ │ │ │ -9F0CE Filename Length 0016 (22) │ │ │ │ -9F0D0 Extra Length 001C (28) │ │ │ │ -9F0D2 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F0D2: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F0E8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F0EA Length 0009 (9) │ │ │ │ -9F0EC Flags 03 (3) 'Modification Access' │ │ │ │ -9F0ED Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F0F1 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F0F5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F0F7 Length 000B (11) │ │ │ │ -9F0F9 Version 01 (1) │ │ │ │ -9F0FA UID Size 04 (4) │ │ │ │ -9F0FB UID 00000000 (0) │ │ │ │ -9F0FF GID Size 04 (4) │ │ │ │ -9F100 GID 00000000 (0) │ │ │ │ -9F104 PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ +04B6D LOCAL HEADER #5 04034B50 (67324752) │ │ │ │ +04B71 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +04B72 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +04B73 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +04B75 Compression Method 0008 (8) 'Deflated' │ │ │ │ +04B77 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +04B7B CRC AFEB34EA (2951427306) │ │ │ │ +04B7F Compressed Size 000003F0 (1008) │ │ │ │ +04B83 Uncompressed Size 00000877 (2167) │ │ │ │ +04B87 Filename Length 0014 (20) │ │ │ │ +04B89 Extra Length 001C (28) │ │ │ │ +04B8B Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4B8B: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +04B9F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +04BA1 Length 0009 (9) │ │ │ │ +04BA3 Flags 03 (3) 'Modification Access' │ │ │ │ +04BA4 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +04BA8 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +04BAC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +04BAE Length 000B (11) │ │ │ │ +04BB0 Version 01 (1) │ │ │ │ +04BB1 UID Size 04 (4) │ │ │ │ +04BB2 UID 00000000 (0) │ │ │ │ +04BB6 GID Size 04 (4) │ │ │ │ +04BB7 GID 00000000 (0) │ │ │ │ +04BBB PAYLOAD │ │ │ │ + │ │ │ │ +04FAB LOCAL HEADER #6 04034B50 (67324752) │ │ │ │ +04FAF Extract Zip Spec 14 (20) '2.0' │ │ │ │ +04FB0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +04FB1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +04FB3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +04FB5 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +04FB9 CRC 21AF85A7 (565151143) │ │ │ │ +04FBD Compressed Size 000001AE (430) │ │ │ │ +04FC1 Uncompressed Size 000002FE (766) │ │ │ │ +04FC5 Filename Length 0011 (17) │ │ │ │ +04FC7 Extra Length 001C (28) │ │ │ │ +04FC9 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4FC9: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +04FDA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +04FDC Length 0009 (9) │ │ │ │ +04FDE Flags 03 (3) 'Modification Access' │ │ │ │ +04FDF Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +04FE3 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +04FE7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +04FE9 Length 000B (11) │ │ │ │ +04FEB Version 01 (1) │ │ │ │ +04FEC UID Size 04 (4) │ │ │ │ +04FED UID 00000000 (0) │ │ │ │ +04FF1 GID Size 04 (4) │ │ │ │ +04FF2 GID 00000000 (0) │ │ │ │ +04FF6 PAYLOAD │ │ │ │ + │ │ │ │ +051A4 LOCAL HEADER #7 04034B50 (67324752) │ │ │ │ +051A8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +051A9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +051AA General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +051AC Compression Method 0008 (8) 'Deflated' │ │ │ │ +051AE Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +051B2 CRC D70AFB1E (3607821086) │ │ │ │ +051B6 Compressed Size 000020BF (8383) │ │ │ │ +051BA Uncompressed Size 0000B4AC (46252) │ │ │ │ +051BE Filename Length 001B (27) │ │ │ │ +051C0 Extra Length 001C (28) │ │ │ │ +051C2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x51C2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +051DD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +051DF Length 0009 (9) │ │ │ │ +051E1 Flags 03 (3) 'Modification Access' │ │ │ │ +051E2 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +051E6 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +051EA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +051EC Length 000B (11) │ │ │ │ +051EE Version 01 (1) │ │ │ │ +051EF UID Size 04 (4) │ │ │ │ +051F0 UID 00000000 (0) │ │ │ │ +051F4 GID Size 04 (4) │ │ │ │ +051F5 GID 00000000 (0) │ │ │ │ +051F9 PAYLOAD │ │ │ │ + │ │ │ │ +072B8 LOCAL HEADER #8 04034B50 (67324752) │ │ │ │ +072BC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +072BD Extract OS 00 (0) 'MS-DOS' │ │ │ │ +072BE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +072C0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +072C2 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +072C6 CRC 2D371F1E (758587166) │ │ │ │ +072CA Compressed Size 00000E70 (3696) │ │ │ │ +072CE Uncompressed Size 000030B3 (12467) │ │ │ │ +072D2 Filename Length 001D (29) │ │ │ │ +072D4 Extra Length 001C (28) │ │ │ │ +072D6 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x72D6: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +072F3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +072F5 Length 0009 (9) │ │ │ │ +072F7 Flags 03 (3) 'Modification Access' │ │ │ │ +072F8 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +072FC Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +07300 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +07302 Length 000B (11) │ │ │ │ +07304 Version 01 (1) │ │ │ │ +07305 UID Size 04 (4) │ │ │ │ +07306 UID 00000000 (0) │ │ │ │ +0730A GID Size 04 (4) │ │ │ │ +0730B GID 00000000 (0) │ │ │ │ +0730F PAYLOAD │ │ │ │ + │ │ │ │ +0817F LOCAL HEADER #9 04034B50 (67324752) │ │ │ │ +08183 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +08184 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +08185 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +08187 Compression Method 0008 (8) 'Deflated' │ │ │ │ +08189 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +0818D CRC 7903D0CB (2030293195) │ │ │ │ +08191 Compressed Size 00000973 (2419) │ │ │ │ +08195 Uncompressed Size 00001CB3 (7347) │ │ │ │ +08199 Filename Length 0019 (25) │ │ │ │ +0819B Extra Length 001C (28) │ │ │ │ +0819D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x819D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +081B6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +081B8 Length 0009 (9) │ │ │ │ +081BA Flags 03 (3) 'Modification Access' │ │ │ │ +081BB Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +081BF Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +081C3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +081C5 Length 000B (11) │ │ │ │ +081C7 Version 01 (1) │ │ │ │ +081C8 UID Size 04 (4) │ │ │ │ +081C9 UID 00000000 (0) │ │ │ │ +081CD GID Size 04 (4) │ │ │ │ +081CE GID 00000000 (0) │ │ │ │ +081D2 PAYLOAD │ │ │ │ + │ │ │ │ +08B45 LOCAL HEADER #10 04034B50 (67324752) │ │ │ │ +08B49 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +08B4A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +08B4B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +08B4D Compression Method 0008 (8) 'Deflated' │ │ │ │ +08B4F Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +08B53 CRC 6D2A31E6 (1831481830) │ │ │ │ +08B57 Compressed Size 00003866 (14438) │ │ │ │ +08B5B Uncompressed Size 0000F7B0 (63408) │ │ │ │ +08B5F Filename Length 0015 (21) │ │ │ │ +08B61 Extra Length 001C (28) │ │ │ │ +08B63 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8B63: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +08B78 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +08B7A Length 0009 (9) │ │ │ │ +08B7C Flags 03 (3) 'Modification Access' │ │ │ │ +08B7D Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +08B81 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +08B85 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +08B87 Length 000B (11) │ │ │ │ +08B89 Version 01 (1) │ │ │ │ +08B8A UID Size 04 (4) │ │ │ │ +08B8B UID 00000000 (0) │ │ │ │ +08B8F GID Size 04 (4) │ │ │ │ +08B90 GID 00000000 (0) │ │ │ │ +08B94 PAYLOAD │ │ │ │ + │ │ │ │ +0C3FA LOCAL HEADER #11 04034B50 (67324752) │ │ │ │ +0C3FE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +0C3FF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +0C400 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +0C402 Compression Method 0008 (8) 'Deflated' │ │ │ │ +0C404 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +0C408 CRC 1D369EB9 (490118841) │ │ │ │ +0C40C Compressed Size 0000A9BC (43452) │ │ │ │ +0C410 Uncompressed Size 0003D7D6 (251862) │ │ │ │ +0C414 Filename Length 0012 (18) │ │ │ │ +0C416 Extra Length 001C (28) │ │ │ │ +0C418 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xC418: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +0C42A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +0C42C Length 0009 (9) │ │ │ │ +0C42E Flags 03 (3) 'Modification Access' │ │ │ │ +0C42F Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +0C433 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +0C437 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +0C439 Length 000B (11) │ │ │ │ +0C43B Version 01 (1) │ │ │ │ +0C43C UID Size 04 (4) │ │ │ │ +0C43D UID 00000000 (0) │ │ │ │ +0C441 GID Size 04 (4) │ │ │ │ +0C442 GID 00000000 (0) │ │ │ │ +0C446 PAYLOAD │ │ │ │ + │ │ │ │ +16E02 LOCAL HEADER #12 04034B50 (67324752) │ │ │ │ +16E06 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +16E07 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +16E08 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +16E0A Compression Method 0008 (8) 'Deflated' │ │ │ │ +16E0C Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +16E10 CRC 464C78A5 (1179416741) │ │ │ │ +16E14 Compressed Size 00003B2A (15146) │ │ │ │ +16E18 Uncompressed Size 0001B412 (111634) │ │ │ │ +16E1C Filename Length 0015 (21) │ │ │ │ +16E1E Extra Length 001C (28) │ │ │ │ +16E20 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x16E20: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +16E35 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +16E37 Length 0009 (9) │ │ │ │ +16E39 Flags 03 (3) 'Modification Access' │ │ │ │ +16E3A Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +16E3E Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +16E42 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +16E44 Length 000B (11) │ │ │ │ +16E46 Version 01 (1) │ │ │ │ +16E47 UID Size 04 (4) │ │ │ │ +16E48 UID 00000000 (0) │ │ │ │ +16E4C GID Size 04 (4) │ │ │ │ +16E4D GID 00000000 (0) │ │ │ │ +16E51 PAYLOAD │ │ │ │ + │ │ │ │ +1A97B LOCAL HEADER #13 04034B50 (67324752) │ │ │ │ +1A97F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +1A980 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +1A981 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +1A983 Compression Method 0008 (8) 'Deflated' │ │ │ │ +1A985 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +1A989 CRC C30A3E97 (3272228503) │ │ │ │ +1A98D Compressed Size 0000915B (37211) │ │ │ │ +1A991 Uncompressed Size 0003D921 (252193) │ │ │ │ +1A995 Filename Length 0014 (20) │ │ │ │ +1A997 Extra Length 001C (28) │ │ │ │ +1A999 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x1A999: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +1A9AD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +1A9AF Length 0009 (9) │ │ │ │ +1A9B1 Flags 03 (3) 'Modification Access' │ │ │ │ +1A9B2 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +1A9B6 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +1A9BA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +1A9BC Length 000B (11) │ │ │ │ +1A9BE Version 01 (1) │ │ │ │ +1A9BF UID Size 04 (4) │ │ │ │ +1A9C0 UID 00000000 (0) │ │ │ │ +1A9C4 GID Size 04 (4) │ │ │ │ +1A9C5 GID 00000000 (0) │ │ │ │ +1A9C9 PAYLOAD │ │ │ │ + │ │ │ │ +23B24 LOCAL HEADER #14 04034B50 (67324752) │ │ │ │ +23B28 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +23B29 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +23B2A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +23B2C Compression Method 0008 (8) 'Deflated' │ │ │ │ +23B2E Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +23B32 CRC 8F9E34DE (2409510110) │ │ │ │ +23B36 Compressed Size 00002A76 (10870) │ │ │ │ +23B3A Uncompressed Size 00011520 (70944) │ │ │ │ +23B3E Filename Length 0016 (22) │ │ │ │ +23B40 Extra Length 001C (28) │ │ │ │ +23B42 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x23B42: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +23B58 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +23B5A Length 0009 (9) │ │ │ │ +23B5C Flags 03 (3) 'Modification Access' │ │ │ │ +23B5D Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +23B61 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +23B65 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +23B67 Length 000B (11) │ │ │ │ +23B69 Version 01 (1) │ │ │ │ +23B6A UID Size 04 (4) │ │ │ │ +23B6B UID 00000000 (0) │ │ │ │ +23B6F GID Size 04 (4) │ │ │ │ +23B70 GID 00000000 (0) │ │ │ │ +23B74 PAYLOAD │ │ │ │ + │ │ │ │ +265EA LOCAL HEADER #15 04034B50 (67324752) │ │ │ │ +265EE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +265EF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +265F0 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +265F2 Compression Method 0008 (8) 'Deflated' │ │ │ │ +265F4 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +265F8 CRC 1BE06878 (467691640) │ │ │ │ +265FC Compressed Size 000014DC (5340) │ │ │ │ +26600 Uncompressed Size 0000518E (20878) │ │ │ │ +26604 Filename Length 001D (29) │ │ │ │ +26606 Extra Length 001C (28) │ │ │ │ +26608 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x26608: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +26625 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +26627 Length 0009 (9) │ │ │ │ +26629 Flags 03 (3) 'Modification Access' │ │ │ │ +2662A Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +2662E Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +26632 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +26634 Length 000B (11) │ │ │ │ +26636 Version 01 (1) │ │ │ │ +26637 UID Size 04 (4) │ │ │ │ +26638 UID 00000000 (0) │ │ │ │ +2663C GID Size 04 (4) │ │ │ │ +2663D GID 00000000 (0) │ │ │ │ +26641 PAYLOAD │ │ │ │ + │ │ │ │ +27B1D LOCAL HEADER #16 04034B50 (67324752) │ │ │ │ +27B21 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +27B22 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +27B23 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +27B25 Compression Method 0008 (8) 'Deflated' │ │ │ │ +27B27 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +27B2B CRC CA50693D (3394267453) │ │ │ │ +27B2F Compressed Size 000039C8 (14792) │ │ │ │ +27B33 Uncompressed Size 0000F03B (61499) │ │ │ │ +27B37 Filename Length 001C (28) │ │ │ │ +27B39 Extra Length 001C (28) │ │ │ │ +27B3B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x27B3B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +27B57 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +27B59 Length 0009 (9) │ │ │ │ +27B5B Flags 03 (3) 'Modification Access' │ │ │ │ +27B5C Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +27B60 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +27B64 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +27B66 Length 000B (11) │ │ │ │ +27B68 Version 01 (1) │ │ │ │ +27B69 UID Size 04 (4) │ │ │ │ +27B6A UID 00000000 (0) │ │ │ │ +27B6E GID Size 04 (4) │ │ │ │ +27B6F GID 00000000 (0) │ │ │ │ +27B73 PAYLOAD │ │ │ │ + │ │ │ │ +2B53B LOCAL HEADER #17 04034B50 (67324752) │ │ │ │ +2B53F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +2B540 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +2B541 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +2B543 Compression Method 0008 (8) 'Deflated' │ │ │ │ +2B545 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +2B549 CRC 7A88ED58 (2055794008) │ │ │ │ +2B54D Compressed Size 000006A3 (1699) │ │ │ │ +2B551 Uncompressed Size 000011F5 (4597) │ │ │ │ +2B555 Filename Length 001C (28) │ │ │ │ +2B557 Extra Length 001C (28) │ │ │ │ +2B559 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x2B559: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +2B575 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +2B577 Length 0009 (9) │ │ │ │ +2B579 Flags 03 (3) 'Modification Access' │ │ │ │ +2B57A Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +2B57E Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +2B582 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +2B584 Length 000B (11) │ │ │ │ +2B586 Version 01 (1) │ │ │ │ +2B587 UID Size 04 (4) │ │ │ │ +2B588 UID 00000000 (0) │ │ │ │ +2B58C GID Size 04 (4) │ │ │ │ +2B58D GID 00000000 (0) │ │ │ │ +2B591 PAYLOAD │ │ │ │ + │ │ │ │ +2BC34 LOCAL HEADER #18 04034B50 (67324752) │ │ │ │ +2BC38 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +2BC39 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +2BC3A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +2BC3C Compression Method 0008 (8) 'Deflated' │ │ │ │ +2BC3E Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +2BC42 CRC D8039A55 (3624114773) │ │ │ │ +2BC46 Compressed Size 0000107D (4221) │ │ │ │ +2BC4A Uncompressed Size 00004BE9 (19433) │ │ │ │ +2BC4E Filename Length 001B (27) │ │ │ │ +2BC50 Extra Length 001C (28) │ │ │ │ +2BC52 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x2BC52: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +2BC6D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +2BC6F Length 0009 (9) │ │ │ │ +2BC71 Flags 03 (3) 'Modification Access' │ │ │ │ +2BC72 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +2BC76 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +2BC7A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +2BC7C Length 000B (11) │ │ │ │ +2BC7E Version 01 (1) │ │ │ │ +2BC7F UID Size 04 (4) │ │ │ │ +2BC80 UID 00000000 (0) │ │ │ │ +2BC84 GID Size 04 (4) │ │ │ │ +2BC85 GID 00000000 (0) │ │ │ │ +2BC89 PAYLOAD │ │ │ │ + │ │ │ │ +2CD06 LOCAL HEADER #19 04034B50 (67324752) │ │ │ │ +2CD0A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +2CD0B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +2CD0C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +2CD0E Compression Method 0008 (8) 'Deflated' │ │ │ │ +2CD10 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +2CD14 CRC 41197FD7 (1092190167) │ │ │ │ +2CD18 Compressed Size 000033AC (13228) │ │ │ │ +2CD1C Uncompressed Size 0000BC95 (48277) │ │ │ │ +2CD20 Filename Length 001D (29) │ │ │ │ +2CD22 Extra Length 001C (28) │ │ │ │ +2CD24 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x2CD24: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +2CD41 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +2CD43 Length 0009 (9) │ │ │ │ +2CD45 Flags 03 (3) 'Modification Access' │ │ │ │ +2CD46 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +2CD4A Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +2CD4E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +2CD50 Length 000B (11) │ │ │ │ +2CD52 Version 01 (1) │ │ │ │ +2CD53 UID Size 04 (4) │ │ │ │ +2CD54 UID 00000000 (0) │ │ │ │ +2CD58 GID Size 04 (4) │ │ │ │ +2CD59 GID 00000000 (0) │ │ │ │ +2CD5D PAYLOAD │ │ │ │ + │ │ │ │ +30109 LOCAL HEADER #20 04034B50 (67324752) │ │ │ │ +3010D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3010E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3010F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +30111 Compression Method 0008 (8) 'Deflated' │ │ │ │ +30113 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +30117 CRC 338EF5C5 (865007045) │ │ │ │ +3011B Compressed Size 00000D6F (3439) │ │ │ │ +3011F Uncompressed Size 0000388E (14478) │ │ │ │ +30123 Filename Length 001D (29) │ │ │ │ +30125 Extra Length 001C (28) │ │ │ │ +30127 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x30127: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +30144 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +30146 Length 0009 (9) │ │ │ │ +30148 Flags 03 (3) 'Modification Access' │ │ │ │ +30149 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +3014D Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +30151 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +30153 Length 000B (11) │ │ │ │ +30155 Version 01 (1) │ │ │ │ +30156 UID Size 04 (4) │ │ │ │ +30157 UID 00000000 (0) │ │ │ │ +3015B GID Size 04 (4) │ │ │ │ +3015C GID 00000000 (0) │ │ │ │ +30160 PAYLOAD │ │ │ │ + │ │ │ │ +30ECF LOCAL HEADER #21 04034B50 (67324752) │ │ │ │ +30ED3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +30ED4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +30ED5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +30ED7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +30ED9 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +30EDD CRC 0AA066F0 (178284272) │ │ │ │ +30EE1 Compressed Size 00001C89 (7305) │ │ │ │ +30EE5 Uncompressed Size 0000C144 (49476) │ │ │ │ +30EE9 Filename Length 001A (26) │ │ │ │ +30EEB Extra Length 001C (28) │ │ │ │ +30EED Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x30EED: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +30F07 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +30F09 Length 0009 (9) │ │ │ │ +30F0B Flags 03 (3) 'Modification Access' │ │ │ │ +30F0C Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +30F10 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +30F14 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +30F16 Length 000B (11) │ │ │ │ +30F18 Version 01 (1) │ │ │ │ +30F19 UID Size 04 (4) │ │ │ │ +30F1A UID 00000000 (0) │ │ │ │ +30F1E GID Size 04 (4) │ │ │ │ +30F1F GID 00000000 (0) │ │ │ │ +30F23 PAYLOAD │ │ │ │ + │ │ │ │ +32BAC LOCAL HEADER #22 04034B50 (67324752) │ │ │ │ +32BB0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +32BB1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +32BB2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +32BB4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +32BB6 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +32BBA CRC C41B95CB (3290142155) │ │ │ │ +32BBE Compressed Size 000003A4 (932) │ │ │ │ +32BC2 Uncompressed Size 0000088F (2191) │ │ │ │ +32BC6 Filename Length 0012 (18) │ │ │ │ +32BC8 Extra Length 001C (28) │ │ │ │ +32BCA Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x32BCA: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +32BDC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +32BDE Length 0009 (9) │ │ │ │ +32BE0 Flags 03 (3) 'Modification Access' │ │ │ │ +32BE1 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +32BE5 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +32BE9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +32BEB Length 000B (11) │ │ │ │ +32BED Version 01 (1) │ │ │ │ +32BEE UID Size 04 (4) │ │ │ │ +32BEF UID 00000000 (0) │ │ │ │ +32BF3 GID Size 04 (4) │ │ │ │ +32BF4 GID 00000000 (0) │ │ │ │ +32BF8 PAYLOAD │ │ │ │ + │ │ │ │ +32F9C LOCAL HEADER #23 04034B50 (67324752) │ │ │ │ +32FA0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +32FA1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +32FA2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +32FA4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +32FA6 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +32FAA CRC 40B68220 (1085702688) │ │ │ │ +32FAE Compressed Size 000001D4 (468) │ │ │ │ +32FB2 Uncompressed Size 00000312 (786) │ │ │ │ +32FB6 Filename Length 0020 (32) │ │ │ │ +32FB8 Extra Length 001C (28) │ │ │ │ +32FBA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x32FBA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +32FDA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +32FDC Length 0009 (9) │ │ │ │ +32FDE Flags 03 (3) 'Modification Access' │ │ │ │ +32FDF Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +32FE3 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +32FE7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +32FE9 Length 000B (11) │ │ │ │ +32FEB Version 01 (1) │ │ │ │ +32FEC UID Size 04 (4) │ │ │ │ +32FED UID 00000000 (0) │ │ │ │ +32FF1 GID Size 04 (4) │ │ │ │ +32FF2 GID 00000000 (0) │ │ │ │ +32FF6 PAYLOAD │ │ │ │ + │ │ │ │ +331CA LOCAL HEADER #24 04034B50 (67324752) │ │ │ │ +331CE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +331CF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +331D0 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +331D2 Compression Method 0008 (8) 'Deflated' │ │ │ │ +331D4 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +331D8 CRC 404001E8 (1077936616) │ │ │ │ +331DC Compressed Size 000017AD (6061) │ │ │ │ +331E0 Uncompressed Size 00009D19 (40217) │ │ │ │ +331E4 Filename Length 001B (27) │ │ │ │ +331E6 Extra Length 001C (28) │ │ │ │ +331E8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x331E8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +33203 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +33205 Length 0009 (9) │ │ │ │ +33207 Flags 03 (3) 'Modification Access' │ │ │ │ +33208 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +3320C Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +33210 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +33212 Length 000B (11) │ │ │ │ +33214 Version 01 (1) │ │ │ │ +33215 UID Size 04 (4) │ │ │ │ +33216 UID 00000000 (0) │ │ │ │ +3321A GID Size 04 (4) │ │ │ │ +3321B GID 00000000 (0) │ │ │ │ +3321F PAYLOAD │ │ │ │ + │ │ │ │ +349CC LOCAL HEADER #25 04034B50 (67324752) │ │ │ │ +349D0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +349D1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +349D2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +349D4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +349D6 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +349DA CRC 8B976C32 (2341956658) │ │ │ │ +349DE Compressed Size 00001374 (4980) │ │ │ │ +349E2 Uncompressed Size 00003B67 (15207) │ │ │ │ +349E6 Filename Length 0015 (21) │ │ │ │ +349E8 Extra Length 001C (28) │ │ │ │ +349EA Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x349EA: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +349FF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +34A01 Length 0009 (9) │ │ │ │ +34A03 Flags 03 (3) 'Modification Access' │ │ │ │ +34A04 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +34A08 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +34A0C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +34A0E Length 000B (11) │ │ │ │ +34A10 Version 01 (1) │ │ │ │ +34A11 UID Size 04 (4) │ │ │ │ +34A12 UID 00000000 (0) │ │ │ │ +34A16 GID Size 04 (4) │ │ │ │ +34A17 GID 00000000 (0) │ │ │ │ +34A1B PAYLOAD │ │ │ │ + │ │ │ │ +35D8F LOCAL HEADER #26 04034B50 (67324752) │ │ │ │ +35D93 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +35D94 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +35D95 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +35D97 Compression Method 0008 (8) 'Deflated' │ │ │ │ +35D99 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +35D9D CRC 2ACCA91C (718055708) │ │ │ │ +35DA1 Compressed Size 00000AD3 (2771) │ │ │ │ +35DA5 Uncompressed Size 00002136 (8502) │ │ │ │ +35DA9 Filename Length 0011 (17) │ │ │ │ +35DAB Extra Length 001C (28) │ │ │ │ +35DAD Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x35DAD: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +35DBE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +35DC0 Length 0009 (9) │ │ │ │ +35DC2 Flags 03 (3) 'Modification Access' │ │ │ │ +35DC3 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +35DC7 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +35DCB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +35DCD Length 000B (11) │ │ │ │ +35DCF Version 01 (1) │ │ │ │ +35DD0 UID Size 04 (4) │ │ │ │ +35DD1 UID 00000000 (0) │ │ │ │ +35DD5 GID Size 04 (4) │ │ │ │ +35DD6 GID 00000000 (0) │ │ │ │ +35DDA PAYLOAD │ │ │ │ + │ │ │ │ +368AD LOCAL HEADER #27 04034B50 (67324752) │ │ │ │ +368B1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +368B2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +368B3 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +368B5 Compression Method 0008 (8) 'Deflated' │ │ │ │ +368B7 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +368BB CRC E7227DBB (3877797307) │ │ │ │ +368BF Compressed Size 000003FE (1022) │ │ │ │ +368C3 Uncompressed Size 00000F0D (3853) │ │ │ │ +368C7 Filename Length 0014 (20) │ │ │ │ +368C9 Extra Length 001C (28) │ │ │ │ +368CB Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x368CB: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +368DF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +368E1 Length 0009 (9) │ │ │ │ +368E3 Flags 03 (3) 'Modification Access' │ │ │ │ +368E4 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +368E8 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +368EC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +368EE Length 000B (11) │ │ │ │ +368F0 Version 01 (1) │ │ │ │ +368F1 UID Size 04 (4) │ │ │ │ +368F2 UID 00000000 (0) │ │ │ │ +368F6 GID Size 04 (4) │ │ │ │ +368F7 GID 00000000 (0) │ │ │ │ +368FB PAYLOAD │ │ │ │ + │ │ │ │ +36CF9 LOCAL HEADER #28 04034B50 (67324752) │ │ │ │ +36CFD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +36CFE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +36CFF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +36D01 Compression Method 0008 (8) 'Deflated' │ │ │ │ +36D03 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +36D07 CRC F2ED2F43 (4075630403) │ │ │ │ +36D0B Compressed Size 00001263 (4707) │ │ │ │ +36D0F Uncompressed Size 0000346A (13418) │ │ │ │ +36D13 Filename Length 0014 (20) │ │ │ │ +36D15 Extra Length 001C (28) │ │ │ │ +36D17 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x36D17: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +36D2B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +36D2D Length 0009 (9) │ │ │ │ +36D2F Flags 03 (3) 'Modification Access' │ │ │ │ +36D30 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +36D34 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +36D38 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +36D3A Length 000B (11) │ │ │ │ +36D3C Version 01 (1) │ │ │ │ +36D3D UID Size 04 (4) │ │ │ │ +36D3E UID 00000000 (0) │ │ │ │ +36D42 GID Size 04 (4) │ │ │ │ +36D43 GID 00000000 (0) │ │ │ │ +36D47 PAYLOAD │ │ │ │ + │ │ │ │ +37FAA LOCAL HEADER #29 04034B50 (67324752) │ │ │ │ +37FAE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +37FAF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +37FB0 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +37FB2 Compression Method 0008 (8) 'Deflated' │ │ │ │ +37FB4 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +37FB8 CRC B778F93A (3078158650) │ │ │ │ +37FBC Compressed Size 00000AD0 (2768) │ │ │ │ +37FC0 Uncompressed Size 00002300 (8960) │ │ │ │ +37FC4 Filename Length 001B (27) │ │ │ │ +37FC6 Extra Length 001C (28) │ │ │ │ +37FC8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x37FC8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +37FE3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +37FE5 Length 0009 (9) │ │ │ │ +37FE7 Flags 03 (3) 'Modification Access' │ │ │ │ +37FE8 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +37FEC Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +37FF0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +37FF2 Length 000B (11) │ │ │ │ +37FF4 Version 01 (1) │ │ │ │ +37FF5 UID Size 04 (4) │ │ │ │ +37FF6 UID 00000000 (0) │ │ │ │ +37FFA GID Size 04 (4) │ │ │ │ +37FFB GID 00000000 (0) │ │ │ │ +37FFF PAYLOAD │ │ │ │ + │ │ │ │ +38ACF LOCAL HEADER #30 04034B50 (67324752) │ │ │ │ +38AD3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +38AD4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +38AD5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +38AD7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +38AD9 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +38ADD CRC 48CED347 (1221514055) │ │ │ │ +38AE1 Compressed Size 00000A94 (2708) │ │ │ │ +38AE5 Uncompressed Size 00002365 (9061) │ │ │ │ +38AE9 Filename Length 0013 (19) │ │ │ │ +38AEB Extra Length 001C (28) │ │ │ │ +38AED Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x38AED: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +38B00 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +38B02 Length 0009 (9) │ │ │ │ +38B04 Flags 03 (3) 'Modification Access' │ │ │ │ +38B05 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +38B09 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +38B0D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +38B0F Length 000B (11) │ │ │ │ +38B11 Version 01 (1) │ │ │ │ +38B12 UID Size 04 (4) │ │ │ │ +38B13 UID 00000000 (0) │ │ │ │ +38B17 GID Size 04 (4) │ │ │ │ +38B18 GID 00000000 (0) │ │ │ │ +38B1C PAYLOAD │ │ │ │ + │ │ │ │ +395B0 LOCAL HEADER #31 04034B50 (67324752) │ │ │ │ +395B4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +395B5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +395B6 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +395B8 Compression Method 0008 (8) 'Deflated' │ │ │ │ +395BA Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +395BE CRC 2FCB6E58 (801861208) │ │ │ │ +395C2 Compressed Size 000010D9 (4313) │ │ │ │ +395C6 Uncompressed Size 000055E3 (21987) │ │ │ │ +395CA Filename Length 000F (15) │ │ │ │ +395CC Extra Length 001C (28) │ │ │ │ +395CE Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x395CE: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +395DD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +395DF Length 0009 (9) │ │ │ │ +395E1 Flags 03 (3) 'Modification Access' │ │ │ │ +395E2 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +395E6 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +395EA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +395EC Length 000B (11) │ │ │ │ +395EE Version 01 (1) │ │ │ │ +395EF UID Size 04 (4) │ │ │ │ +395F0 UID 00000000 (0) │ │ │ │ +395F4 GID Size 04 (4) │ │ │ │ +395F5 GID 00000000 (0) │ │ │ │ +395F9 PAYLOAD │ │ │ │ + │ │ │ │ +3A6D2 LOCAL HEADER #32 04034B50 (67324752) │ │ │ │ +3A6D6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3A6D7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3A6D8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3A6DA Compression Method 0008 (8) 'Deflated' │ │ │ │ +3A6DC Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +3A6E0 CRC C70CE575 (3339511157) │ │ │ │ +3A6E4 Compressed Size 0000066B (1643) │ │ │ │ +3A6E8 Uncompressed Size 000018E0 (6368) │ │ │ │ +3A6EC Filename Length 000F (15) │ │ │ │ +3A6EE Extra Length 001C (28) │ │ │ │ +3A6F0 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3A6F0: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3A6FF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3A701 Length 0009 (9) │ │ │ │ +3A703 Flags 03 (3) 'Modification Access' │ │ │ │ +3A704 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +3A708 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +3A70C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3A70E Length 000B (11) │ │ │ │ +3A710 Version 01 (1) │ │ │ │ +3A711 UID Size 04 (4) │ │ │ │ +3A712 UID 00000000 (0) │ │ │ │ +3A716 GID Size 04 (4) │ │ │ │ +3A717 GID 00000000 (0) │ │ │ │ +3A71B PAYLOAD │ │ │ │ + │ │ │ │ +3AD86 LOCAL HEADER #33 04034B50 (67324752) │ │ │ │ +3AD8A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3AD8B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3AD8C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3AD8E Compression Method 0008 (8) 'Deflated' │ │ │ │ +3AD90 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +3AD94 CRC CEB50239 (3467969081) │ │ │ │ +3AD98 Compressed Size 00001A61 (6753) │ │ │ │ +3AD9C Uncompressed Size 000065AF (26031) │ │ │ │ +3ADA0 Filename Length 0013 (19) │ │ │ │ +3ADA2 Extra Length 001C (28) │ │ │ │ +3ADA4 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3ADA4: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3ADB7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3ADB9 Length 0009 (9) │ │ │ │ +3ADBB Flags 03 (3) 'Modification Access' │ │ │ │ +3ADBC Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +3ADC0 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +3ADC4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3ADC6 Length 000B (11) │ │ │ │ +3ADC8 Version 01 (1) │ │ │ │ +3ADC9 UID Size 04 (4) │ │ │ │ +3ADCA UID 00000000 (0) │ │ │ │ +3ADCE GID Size 04 (4) │ │ │ │ +3ADCF GID 00000000 (0) │ │ │ │ +3ADD3 PAYLOAD │ │ │ │ + │ │ │ │ +3C834 LOCAL HEADER #34 04034B50 (67324752) │ │ │ │ +3C838 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3C839 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3C83A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3C83C Compression Method 0008 (8) 'Deflated' │ │ │ │ +3C83E Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +3C842 CRC BA8C2020 (3129745440) │ │ │ │ +3C846 Compressed Size 000009A7 (2471) │ │ │ │ +3C84A Uncompressed Size 00001B65 (7013) │ │ │ │ +3C84E Filename Length 0010 (16) │ │ │ │ +3C850 Extra Length 001C (28) │ │ │ │ +3C852 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3C852: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3C862 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3C864 Length 0009 (9) │ │ │ │ +3C866 Flags 03 (3) 'Modification Access' │ │ │ │ +3C867 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +3C86B Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +3C86F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3C871 Length 000B (11) │ │ │ │ +3C873 Version 01 (1) │ │ │ │ +3C874 UID Size 04 (4) │ │ │ │ +3C875 UID 00000000 (0) │ │ │ │ +3C879 GID Size 04 (4) │ │ │ │ +3C87A GID 00000000 (0) │ │ │ │ +3C87E PAYLOAD │ │ │ │ + │ │ │ │ +3D225 LOCAL HEADER #35 04034B50 (67324752) │ │ │ │ +3D229 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3D22A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3D22B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3D22D Compression Method 0008 (8) 'Deflated' │ │ │ │ +3D22F Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +3D233 CRC 61869F50 (1636212560) │ │ │ │ +3D237 Compressed Size 000006B9 (1721) │ │ │ │ +3D23B Uncompressed Size 00001566 (5478) │ │ │ │ +3D23F Filename Length 0012 (18) │ │ │ │ +3D241 Extra Length 001C (28) │ │ │ │ +3D243 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3D243: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3D255 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3D257 Length 0009 (9) │ │ │ │ +3D259 Flags 03 (3) 'Modification Access' │ │ │ │ +3D25A Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +3D25E Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +3D262 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3D264 Length 000B (11) │ │ │ │ +3D266 Version 01 (1) │ │ │ │ +3D267 UID Size 04 (4) │ │ │ │ +3D268 UID 00000000 (0) │ │ │ │ +3D26C GID Size 04 (4) │ │ │ │ +3D26D GID 00000000 (0) │ │ │ │ +3D271 PAYLOAD │ │ │ │ + │ │ │ │ +3D92A LOCAL HEADER #36 04034B50 (67324752) │ │ │ │ +3D92E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +3D92F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +3D930 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +3D932 Compression Method 0008 (8) 'Deflated' │ │ │ │ +3D934 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +3D938 CRC 42F0CC6A (1123077226) │ │ │ │ +3D93C Compressed Size 00002A18 (10776) │ │ │ │ +3D940 Uncompressed Size 0000B1DD (45533) │ │ │ │ +3D944 Filename Length 0010 (16) │ │ │ │ +3D946 Extra Length 001C (28) │ │ │ │ +3D948 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x3D948: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +3D958 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +3D95A Length 0009 (9) │ │ │ │ +3D95C Flags 03 (3) 'Modification Access' │ │ │ │ +3D95D Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +3D961 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +3D965 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +3D967 Length 000B (11) │ │ │ │ +3D969 Version 01 (1) │ │ │ │ +3D96A UID Size 04 (4) │ │ │ │ +3D96B UID 00000000 (0) │ │ │ │ +3D96F GID Size 04 (4) │ │ │ │ +3D970 GID 00000000 (0) │ │ │ │ +3D974 PAYLOAD │ │ │ │ + │ │ │ │ +4038C LOCAL HEADER #37 04034B50 (67324752) │ │ │ │ +40390 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +40391 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +40392 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +40394 Compression Method 0008 (8) 'Deflated' │ │ │ │ +40396 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +4039A CRC 25DE9EF7 (635346679) │ │ │ │ +4039E Compressed Size 00001E8C (7820) │ │ │ │ +403A2 Uncompressed Size 00009940 (39232) │ │ │ │ +403A6 Filename Length 0012 (18) │ │ │ │ +403A8 Extra Length 001C (28) │ │ │ │ +403AA Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x403AA: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +403BC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +403BE Length 0009 (9) │ │ │ │ +403C0 Flags 03 (3) 'Modification Access' │ │ │ │ +403C1 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +403C5 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +403C9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +403CB Length 000B (11) │ │ │ │ +403CD Version 01 (1) │ │ │ │ +403CE UID Size 04 (4) │ │ │ │ +403CF UID 00000000 (0) │ │ │ │ +403D3 GID Size 04 (4) │ │ │ │ +403D4 GID 00000000 (0) │ │ │ │ +403D8 PAYLOAD │ │ │ │ + │ │ │ │ +42264 LOCAL HEADER #38 04034B50 (67324752) │ │ │ │ +42268 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +42269 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4226A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4226C Compression Method 0008 (8) 'Deflated' │ │ │ │ +4226E Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +42272 CRC 0F5462F7 (257188599) │ │ │ │ +42276 Compressed Size 0000144E (5198) │ │ │ │ +4227A Uncompressed Size 00007A46 (31302) │ │ │ │ +4227E Filename Length 0018 (24) │ │ │ │ +42280 Extra Length 001C (28) │ │ │ │ +42282 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x42282: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +4229A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +4229C Length 0009 (9) │ │ │ │ +4229E Flags 03 (3) 'Modification Access' │ │ │ │ +4229F Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +422A3 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +422A7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +422A9 Length 000B (11) │ │ │ │ +422AB Version 01 (1) │ │ │ │ +422AC UID Size 04 (4) │ │ │ │ +422AD UID 00000000 (0) │ │ │ │ +422B1 GID Size 04 (4) │ │ │ │ +422B2 GID 00000000 (0) │ │ │ │ +422B6 PAYLOAD │ │ │ │ + │ │ │ │ +43704 LOCAL HEADER #39 04034B50 (67324752) │ │ │ │ +43708 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +43709 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4370A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4370C Compression Method 0008 (8) 'Deflated' │ │ │ │ +4370E Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +43712 CRC 4D59ECAE (1297738926) │ │ │ │ +43716 Compressed Size 000018DB (6363) │ │ │ │ +4371A Uncompressed Size 0000A83A (43066) │ │ │ │ +4371E Filename Length 001F (31) │ │ │ │ +43720 Extra Length 001C (28) │ │ │ │ +43722 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x43722: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +43741 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +43743 Length 0009 (9) │ │ │ │ +43745 Flags 03 (3) 'Modification Access' │ │ │ │ +43746 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +4374A Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +4374E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +43750 Length 000B (11) │ │ │ │ +43752 Version 01 (1) │ │ │ │ +43753 UID Size 04 (4) │ │ │ │ +43754 UID 00000000 (0) │ │ │ │ +43758 GID Size 04 (4) │ │ │ │ +43759 GID 00000000 (0) │ │ │ │ +4375D PAYLOAD │ │ │ │ + │ │ │ │ +45038 LOCAL HEADER #40 04034B50 (67324752) │ │ │ │ +4503C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4503D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4503E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +45040 Compression Method 0008 (8) 'Deflated' │ │ │ │ +45042 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +45046 CRC 8E4EBF9E (2387525534) │ │ │ │ +4504A Compressed Size 000003F8 (1016) │ │ │ │ +4504E Uncompressed Size 000008A4 (2212) │ │ │ │ +45052 Filename Length 001E (30) │ │ │ │ +45054 Extra Length 001C (28) │ │ │ │ +45056 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x45056: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +45074 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +45076 Length 0009 (9) │ │ │ │ +45078 Flags 03 (3) 'Modification Access' │ │ │ │ +45079 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +4507D Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +45081 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +45083 Length 000B (11) │ │ │ │ +45085 Version 01 (1) │ │ │ │ +45086 UID Size 04 (4) │ │ │ │ +45087 UID 00000000 (0) │ │ │ │ +4508B GID Size 04 (4) │ │ │ │ +4508C GID 00000000 (0) │ │ │ │ +45090 PAYLOAD │ │ │ │ + │ │ │ │ +45488 LOCAL HEADER #41 04034B50 (67324752) │ │ │ │ +4548C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4548D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4548E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +45490 Compression Method 0008 (8) 'Deflated' │ │ │ │ +45492 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +45496 CRC D9FD2FF3 (3657248755) │ │ │ │ +4549A Compressed Size 00004296 (17046) │ │ │ │ +4549E Uncompressed Size 0000D8E8 (55528) │ │ │ │ +454A2 Filename Length 0013 (19) │ │ │ │ +454A4 Extra Length 001C (28) │ │ │ │ +454A6 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x454A6: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +454B9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +454BB Length 0009 (9) │ │ │ │ +454BD Flags 03 (3) 'Modification Access' │ │ │ │ +454BE Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +454C2 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +454C6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +454C8 Length 000B (11) │ │ │ │ +454CA Version 01 (1) │ │ │ │ +454CB UID Size 04 (4) │ │ │ │ +454CC UID 00000000 (0) │ │ │ │ +454D0 GID Size 04 (4) │ │ │ │ +454D1 GID 00000000 (0) │ │ │ │ +454D5 PAYLOAD │ │ │ │ + │ │ │ │ +4976B LOCAL HEADER #42 04034B50 (67324752) │ │ │ │ +4976F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +49770 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +49771 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +49773 Compression Method 0008 (8) 'Deflated' │ │ │ │ +49775 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +49779 CRC F2AE95AF (4071527855) │ │ │ │ +4977D Compressed Size 000026C5 (9925) │ │ │ │ +49781 Uncompressed Size 00006E46 (28230) │ │ │ │ +49785 Filename Length 0019 (25) │ │ │ │ +49787 Extra Length 001C (28) │ │ │ │ +49789 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x49789: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +497A2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +497A4 Length 0009 (9) │ │ │ │ +497A6 Flags 03 (3) 'Modification Access' │ │ │ │ +497A7 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +497AB Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +497AF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +497B1 Length 000B (11) │ │ │ │ +497B3 Version 01 (1) │ │ │ │ +497B4 UID Size 04 (4) │ │ │ │ +497B5 UID 00000000 (0) │ │ │ │ +497B9 GID Size 04 (4) │ │ │ │ +497BA GID 00000000 (0) │ │ │ │ +497BE PAYLOAD │ │ │ │ + │ │ │ │ +4BE83 LOCAL HEADER #43 04034B50 (67324752) │ │ │ │ +4BE87 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4BE88 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4BE89 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4BE8B Compression Method 0008 (8) 'Deflated' │ │ │ │ +4BE8D Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +4BE91 CRC A84F7DF4 (2823781876) │ │ │ │ +4BE95 Compressed Size 0000273B (10043) │ │ │ │ +4BE99 Uncompressed Size 00008B84 (35716) │ │ │ │ +4BE9D Filename Length 0019 (25) │ │ │ │ +4BE9F Extra Length 001C (28) │ │ │ │ +4BEA1 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4BEA1: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +4BEBA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +4BEBC Length 0009 (9) │ │ │ │ +4BEBE Flags 03 (3) 'Modification Access' │ │ │ │ +4BEBF Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +4BEC3 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +4BEC7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +4BEC9 Length 000B (11) │ │ │ │ +4BECB Version 01 (1) │ │ │ │ +4BECC UID Size 04 (4) │ │ │ │ +4BECD UID 00000000 (0) │ │ │ │ +4BED1 GID Size 04 (4) │ │ │ │ +4BED2 GID 00000000 (0) │ │ │ │ +4BED6 PAYLOAD │ │ │ │ + │ │ │ │ +4E611 LOCAL HEADER #44 04034B50 (67324752) │ │ │ │ +4E615 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4E616 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4E617 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4E619 Compression Method 0008 (8) 'Deflated' │ │ │ │ +4E61B Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +4E61F CRC 5568CA0F (1432930831) │ │ │ │ +4E623 Compressed Size 00000CF8 (3320) │ │ │ │ +4E627 Uncompressed Size 0000517B (20859) │ │ │ │ +4E62B Filename Length 0021 (33) │ │ │ │ +4E62D Extra Length 001C (28) │ │ │ │ +4E62F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4E62F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +4E650 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +4E652 Length 0009 (9) │ │ │ │ +4E654 Flags 03 (3) 'Modification Access' │ │ │ │ +4E655 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +4E659 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +4E65D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +4E65F Length 000B (11) │ │ │ │ +4E661 Version 01 (1) │ │ │ │ +4E662 UID Size 04 (4) │ │ │ │ +4E663 UID 00000000 (0) │ │ │ │ +4E667 GID Size 04 (4) │ │ │ │ +4E668 GID 00000000 (0) │ │ │ │ +4E66C PAYLOAD │ │ │ │ + │ │ │ │ +4F364 LOCAL HEADER #45 04034B50 (67324752) │ │ │ │ +4F368 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4F369 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4F36A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4F36C Compression Method 0008 (8) 'Deflated' │ │ │ │ +4F36E Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +4F372 CRC 4527F9F0 (1160247792) │ │ │ │ +4F376 Compressed Size 00000469 (1129) │ │ │ │ +4F37A Uncompressed Size 00000932 (2354) │ │ │ │ +4F37E Filename Length 001B (27) │ │ │ │ +4F380 Extra Length 001C (28) │ │ │ │ +4F382 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4F382: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +4F39D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +4F39F Length 0009 (9) │ │ │ │ +4F3A1 Flags 03 (3) 'Modification Access' │ │ │ │ +4F3A2 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +4F3A6 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +4F3AA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +4F3AC Length 000B (11) │ │ │ │ +4F3AE Version 01 (1) │ │ │ │ +4F3AF UID Size 04 (4) │ │ │ │ +4F3B0 UID 00000000 (0) │ │ │ │ +4F3B4 GID Size 04 (4) │ │ │ │ +4F3B5 GID 00000000 (0) │ │ │ │ +4F3B9 PAYLOAD │ │ │ │ + │ │ │ │ +4F822 LOCAL HEADER #46 04034B50 (67324752) │ │ │ │ +4F826 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +4F827 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +4F828 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +4F82A Compression Method 0008 (8) 'Deflated' │ │ │ │ +4F82C Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +4F830 CRC BEA0644D (3198182477) │ │ │ │ +4F834 Compressed Size 00001704 (5892) │ │ │ │ +4F838 Uncompressed Size 00007A12 (31250) │ │ │ │ +4F83C Filename Length 001F (31) │ │ │ │ +4F83E Extra Length 001C (28) │ │ │ │ +4F840 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x4F840: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +4F85F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +4F861 Length 0009 (9) │ │ │ │ +4F863 Flags 03 (3) 'Modification Access' │ │ │ │ +4F864 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +4F868 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +4F86C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +4F86E Length 000B (11) │ │ │ │ +4F870 Version 01 (1) │ │ │ │ +4F871 UID Size 04 (4) │ │ │ │ +4F872 UID 00000000 (0) │ │ │ │ +4F876 GID Size 04 (4) │ │ │ │ +4F877 GID 00000000 (0) │ │ │ │ +4F87B PAYLOAD │ │ │ │ + │ │ │ │ +50F7F LOCAL HEADER #47 04034B50 (67324752) │ │ │ │ +50F83 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +50F84 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +50F85 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +50F87 Compression Method 0008 (8) 'Deflated' │ │ │ │ +50F89 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +50F8D CRC DF4EAE4A (3746475594) │ │ │ │ +50F91 Compressed Size 00004162 (16738) │ │ │ │ +50F95 Uncompressed Size 0001D147 (119111) │ │ │ │ +50F99 Filename Length 0010 (16) │ │ │ │ +50F9B Extra Length 001C (28) │ │ │ │ +50F9D Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x50F9D: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +50FAD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +50FAF Length 0009 (9) │ │ │ │ +50FB1 Flags 03 (3) 'Modification Access' │ │ │ │ +50FB2 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +50FB6 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +50FBA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +50FBC Length 000B (11) │ │ │ │ +50FBE Version 01 (1) │ │ │ │ +50FBF UID Size 04 (4) │ │ │ │ +50FC0 UID 00000000 (0) │ │ │ │ +50FC4 GID Size 04 (4) │ │ │ │ +50FC5 GID 00000000 (0) │ │ │ │ +50FC9 PAYLOAD │ │ │ │ + │ │ │ │ +5512B LOCAL HEADER #48 04034B50 (67324752) │ │ │ │ +5512F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +55130 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +55131 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +55133 Compression Method 0008 (8) 'Deflated' │ │ │ │ +55135 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +55139 CRC 1C486A07 (474507783) │ │ │ │ +5513D Compressed Size 00000A98 (2712) │ │ │ │ +55141 Uncompressed Size 00002106 (8454) │ │ │ │ +55145 Filename Length 0014 (20) │ │ │ │ +55147 Extra Length 001C (28) │ │ │ │ +55149 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x55149: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +5515D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +5515F Length 0009 (9) │ │ │ │ +55161 Flags 03 (3) 'Modification Access' │ │ │ │ +55162 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +55166 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +5516A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +5516C Length 000B (11) │ │ │ │ +5516E Version 01 (1) │ │ │ │ +5516F UID Size 04 (4) │ │ │ │ +55170 UID 00000000 (0) │ │ │ │ +55174 GID Size 04 (4) │ │ │ │ +55175 GID 00000000 (0) │ │ │ │ +55179 PAYLOAD │ │ │ │ + │ │ │ │ +55C11 LOCAL HEADER #49 04034B50 (67324752) │ │ │ │ +55C15 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +55C16 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +55C17 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +55C19 Compression Method 0008 (8) 'Deflated' │ │ │ │ +55C1B Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +55C1F CRC 4A9BE1BB (1251729851) │ │ │ │ +55C23 Compressed Size 0000AC10 (44048) │ │ │ │ +55C27 Uncompressed Size 0003E93F (256319) │ │ │ │ +55C2B Filename Length 0017 (23) │ │ │ │ +55C2D Extra Length 001C (28) │ │ │ │ +55C2F Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x55C2F: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +55C46 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +55C48 Length 0009 (9) │ │ │ │ +55C4A Flags 03 (3) 'Modification Access' │ │ │ │ +55C4B Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +55C4F Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +55C53 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +55C55 Length 000B (11) │ │ │ │ +55C57 Version 01 (1) │ │ │ │ +55C58 UID Size 04 (4) │ │ │ │ +55C59 UID 00000000 (0) │ │ │ │ +55C5D GID Size 04 (4) │ │ │ │ +55C5E GID 00000000 (0) │ │ │ │ +55C62 PAYLOAD │ │ │ │ + │ │ │ │ +60872 LOCAL HEADER #50 04034B50 (67324752) │ │ │ │ +60876 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +60877 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +60878 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6087A Compression Method 0008 (8) 'Deflated' │ │ │ │ +6087C Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +60880 CRC 34458E6B (876973675) │ │ │ │ +60884 Compressed Size 00000461 (1121) │ │ │ │ +60888 Uncompressed Size 00000DF4 (3572) │ │ │ │ +6088C Filename Length 0013 (19) │ │ │ │ +6088E Extra Length 001C (28) │ │ │ │ +60890 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x60890: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +608A3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +608A5 Length 0009 (9) │ │ │ │ +608A7 Flags 03 (3) 'Modification Access' │ │ │ │ +608A8 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +608AC Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +608B0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +608B2 Length 000B (11) │ │ │ │ +608B4 Version 01 (1) │ │ │ │ +608B5 UID Size 04 (4) │ │ │ │ +608B6 UID 00000000 (0) │ │ │ │ +608BA GID Size 04 (4) │ │ │ │ +608BB GID 00000000 (0) │ │ │ │ +608BF PAYLOAD │ │ │ │ + │ │ │ │ +60D20 LOCAL HEADER #51 04034B50 (67324752) │ │ │ │ +60D24 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +60D25 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +60D26 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +60D28 Compression Method 0008 (8) 'Deflated' │ │ │ │ +60D2A Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +60D2E CRC 0F8B3149 (260780361) │ │ │ │ +60D32 Compressed Size 000014B8 (5304) │ │ │ │ +60D36 Uncompressed Size 000067DB (26587) │ │ │ │ +60D3A Filename Length 0012 (18) │ │ │ │ +60D3C Extra Length 001C (28) │ │ │ │ +60D3E Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x60D3E: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +60D50 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +60D52 Length 0009 (9) │ │ │ │ +60D54 Flags 03 (3) 'Modification Access' │ │ │ │ +60D55 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +60D59 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +60D5D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +60D5F Length 000B (11) │ │ │ │ +60D61 Version 01 (1) │ │ │ │ +60D62 UID Size 04 (4) │ │ │ │ +60D63 UID 00000000 (0) │ │ │ │ +60D67 GID Size 04 (4) │ │ │ │ +60D68 GID 00000000 (0) │ │ │ │ +60D6C PAYLOAD │ │ │ │ + │ │ │ │ +62224 LOCAL HEADER #52 04034B50 (67324752) │ │ │ │ +62228 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +62229 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6222A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6222C Compression Method 0008 (8) 'Deflated' │ │ │ │ +6222E Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +62232 CRC 6392F351 (1670574929) │ │ │ │ +62236 Compressed Size 000011F1 (4593) │ │ │ │ +6223A Uncompressed Size 0000410D (16653) │ │ │ │ +6223E Filename Length 0012 (18) │ │ │ │ +62240 Extra Length 001C (28) │ │ │ │ +62242 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x62242: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +62254 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +62256 Length 0009 (9) │ │ │ │ +62258 Flags 03 (3) 'Modification Access' │ │ │ │ +62259 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +6225D Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +62261 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +62263 Length 000B (11) │ │ │ │ +62265 Version 01 (1) │ │ │ │ +62266 UID Size 04 (4) │ │ │ │ +62267 UID 00000000 (0) │ │ │ │ +6226B GID Size 04 (4) │ │ │ │ +6226C GID 00000000 (0) │ │ │ │ +62270 PAYLOAD │ │ │ │ + │ │ │ │ +63461 LOCAL HEADER #53 04034B50 (67324752) │ │ │ │ +63465 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +63466 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +63467 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +63469 Compression Method 0008 (8) 'Deflated' │ │ │ │ +6346B Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +6346F CRC 07B7BFDA (129482714) │ │ │ │ +63473 Compressed Size 000009DA (2522) │ │ │ │ +63477 Uncompressed Size 0000352A (13610) │ │ │ │ +6347B Filename Length 0019 (25) │ │ │ │ +6347D Extra Length 001C (28) │ │ │ │ +6347F Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6347F: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +63498 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6349A Length 0009 (9) │ │ │ │ +6349C Flags 03 (3) 'Modification Access' │ │ │ │ +6349D Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +634A1 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +634A5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +634A7 Length 000B (11) │ │ │ │ +634A9 Version 01 (1) │ │ │ │ +634AA UID Size 04 (4) │ │ │ │ +634AB UID 00000000 (0) │ │ │ │ +634AF GID Size 04 (4) │ │ │ │ +634B0 GID 00000000 (0) │ │ │ │ +634B4 PAYLOAD │ │ │ │ + │ │ │ │ +63E8E LOCAL HEADER #54 04034B50 (67324752) │ │ │ │ +63E92 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +63E93 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +63E94 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +63E96 Compression Method 0008 (8) 'Deflated' │ │ │ │ +63E98 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +63E9C CRC 99B7902F (2578944047) │ │ │ │ +63EA0 Compressed Size 0000203A (8250) │ │ │ │ +63EA4 Uncompressed Size 00010919 (67865) │ │ │ │ +63EA8 Filename Length 0019 (25) │ │ │ │ +63EAA Extra Length 001C (28) │ │ │ │ +63EAC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x63EAC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +63EC5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +63EC7 Length 0009 (9) │ │ │ │ +63EC9 Flags 03 (3) 'Modification Access' │ │ │ │ +63ECA Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +63ECE Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +63ED2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +63ED4 Length 000B (11) │ │ │ │ +63ED6 Version 01 (1) │ │ │ │ +63ED7 UID Size 04 (4) │ │ │ │ +63ED8 UID 00000000 (0) │ │ │ │ +63EDC GID Size 04 (4) │ │ │ │ +63EDD GID 00000000 (0) │ │ │ │ +63EE1 PAYLOAD │ │ │ │ + │ │ │ │ +65F1B LOCAL HEADER #55 04034B50 (67324752) │ │ │ │ +65F1F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +65F20 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +65F21 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +65F23 Compression Method 0008 (8) 'Deflated' │ │ │ │ +65F25 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +65F29 CRC D0372DB1 (3493277105) │ │ │ │ +65F2D Compressed Size 0000177F (6015) │ │ │ │ +65F31 Uncompressed Size 0000472D (18221) │ │ │ │ +65F35 Filename Length 0014 (20) │ │ │ │ +65F37 Extra Length 001C (28) │ │ │ │ +65F39 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x65F39: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +65F4D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +65F4F Length 0009 (9) │ │ │ │ +65F51 Flags 03 (3) 'Modification Access' │ │ │ │ +65F52 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +65F56 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +65F5A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +65F5C Length 000B (11) │ │ │ │ +65F5E Version 01 (1) │ │ │ │ +65F5F UID Size 04 (4) │ │ │ │ +65F60 UID 00000000 (0) │ │ │ │ +65F64 GID Size 04 (4) │ │ │ │ +65F65 GID 00000000 (0) │ │ │ │ +65F69 PAYLOAD │ │ │ │ + │ │ │ │ +676E8 LOCAL HEADER #56 04034B50 (67324752) │ │ │ │ +676EC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +676ED Extract OS 00 (0) 'MS-DOS' │ │ │ │ +676EE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +676F0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +676F2 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +676F6 CRC FAE30038 (4209180728) │ │ │ │ +676FA Compressed Size 0000040B (1035) │ │ │ │ +676FE Uncompressed Size 00000826 (2086) │ │ │ │ +67702 Filename Length 001C (28) │ │ │ │ +67704 Extra Length 001C (28) │ │ │ │ +67706 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x67706: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +67722 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +67724 Length 0009 (9) │ │ │ │ +67726 Flags 03 (3) 'Modification Access' │ │ │ │ +67727 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +6772B Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +6772F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +67731 Length 000B (11) │ │ │ │ +67733 Version 01 (1) │ │ │ │ +67734 UID Size 04 (4) │ │ │ │ +67735 UID 00000000 (0) │ │ │ │ +67739 GID Size 04 (4) │ │ │ │ +6773A GID 00000000 (0) │ │ │ │ +6773E PAYLOAD │ │ │ │ + │ │ │ │ +67B49 LOCAL HEADER #57 04034B50 (67324752) │ │ │ │ +67B4D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +67B4E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +67B4F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +67B51 Compression Method 0008 (8) 'Deflated' │ │ │ │ +67B53 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +67B57 CRC 6F4786FC (1866958588) │ │ │ │ +67B5B Compressed Size 000024A8 (9384) │ │ │ │ +67B5F Uncompressed Size 0000B5F9 (46585) │ │ │ │ +67B63 Filename Length 001F (31) │ │ │ │ +67B65 Extra Length 001C (28) │ │ │ │ +67B67 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x67B67: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +67B86 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +67B88 Length 0009 (9) │ │ │ │ +67B8A Flags 03 (3) 'Modification Access' │ │ │ │ +67B8B Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +67B8F Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +67B93 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +67B95 Length 000B (11) │ │ │ │ +67B97 Version 01 (1) │ │ │ │ +67B98 UID Size 04 (4) │ │ │ │ +67B99 UID 00000000 (0) │ │ │ │ +67B9D GID Size 04 (4) │ │ │ │ +67B9E GID 00000000 (0) │ │ │ │ +67BA2 PAYLOAD │ │ │ │ + │ │ │ │ +6A04A LOCAL HEADER #58 04034B50 (67324752) │ │ │ │ +6A04E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6A04F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6A050 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6A052 Compression Method 0008 (8) 'Deflated' │ │ │ │ +6A054 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +6A058 CRC 60313637 (1613837879) │ │ │ │ +6A05C Compressed Size 00000E5A (3674) │ │ │ │ +6A060 Uncompressed Size 0000527E (21118) │ │ │ │ +6A064 Filename Length 001F (31) │ │ │ │ +6A066 Extra Length 001C (28) │ │ │ │ +6A068 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6A068: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6A087 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6A089 Length 0009 (9) │ │ │ │ +6A08B Flags 03 (3) 'Modification Access' │ │ │ │ +6A08C Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +6A090 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +6A094 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6A096 Length 000B (11) │ │ │ │ +6A098 Version 01 (1) │ │ │ │ +6A099 UID Size 04 (4) │ │ │ │ +6A09A UID 00000000 (0) │ │ │ │ +6A09E GID Size 04 (4) │ │ │ │ +6A09F GID 00000000 (0) │ │ │ │ +6A0A3 PAYLOAD │ │ │ │ + │ │ │ │ +6AEFD LOCAL HEADER #59 04034B50 (67324752) │ │ │ │ +6AF01 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6AF02 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6AF03 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6AF05 Compression Method 0008 (8) 'Deflated' │ │ │ │ +6AF07 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +6AF0B CRC 11754EED (292900589) │ │ │ │ +6AF0F Compressed Size 00000A44 (2628) │ │ │ │ +6AF13 Uncompressed Size 0000244F (9295) │ │ │ │ +6AF17 Filename Length 0013 (19) │ │ │ │ +6AF19 Extra Length 001C (28) │ │ │ │ +6AF1B Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6AF1B: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6AF2E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6AF30 Length 0009 (9) │ │ │ │ +6AF32 Flags 03 (3) 'Modification Access' │ │ │ │ +6AF33 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +6AF37 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +6AF3B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6AF3D Length 000B (11) │ │ │ │ +6AF3F Version 01 (1) │ │ │ │ +6AF40 UID Size 04 (4) │ │ │ │ +6AF41 UID 00000000 (0) │ │ │ │ +6AF45 GID Size 04 (4) │ │ │ │ +6AF46 GID 00000000 (0) │ │ │ │ +6AF4A PAYLOAD │ │ │ │ + │ │ │ │ +6B98E LOCAL HEADER #60 04034B50 (67324752) │ │ │ │ +6B992 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6B993 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6B994 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6B996 Compression Method 0008 (8) 'Deflated' │ │ │ │ +6B998 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +6B99C CRC E5924A27 (3851569703) │ │ │ │ +6B9A0 Compressed Size 00002487 (9351) │ │ │ │ +6B9A4 Uncompressed Size 0000B84D (47181) │ │ │ │ +6B9A8 Filename Length 0019 (25) │ │ │ │ +6B9AA Extra Length 001C (28) │ │ │ │ +6B9AC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6B9AC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6B9C5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6B9C7 Length 0009 (9) │ │ │ │ +6B9C9 Flags 03 (3) 'Modification Access' │ │ │ │ +6B9CA Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +6B9CE Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +6B9D2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6B9D4 Length 000B (11) │ │ │ │ +6B9D6 Version 01 (1) │ │ │ │ +6B9D7 UID Size 04 (4) │ │ │ │ +6B9D8 UID 00000000 (0) │ │ │ │ +6B9DC GID Size 04 (4) │ │ │ │ +6B9DD GID 00000000 (0) │ │ │ │ +6B9E1 PAYLOAD │ │ │ │ + │ │ │ │ +6DE68 LOCAL HEADER #61 04034B50 (67324752) │ │ │ │ +6DE6C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6DE6D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6DE6E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6DE70 Compression Method 0008 (8) 'Deflated' │ │ │ │ +6DE72 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +6DE76 CRC 31F68DC1 (838241729) │ │ │ │ +6DE7A Compressed Size 00000EFC (3836) │ │ │ │ +6DE7E Uncompressed Size 00003A2D (14893) │ │ │ │ +6DE82 Filename Length 0024 (36) │ │ │ │ +6DE84 Extra Length 001C (28) │ │ │ │ +6DE86 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6DE86: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6DEAA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6DEAC Length 0009 (9) │ │ │ │ +6DEAE Flags 03 (3) 'Modification Access' │ │ │ │ +6DEAF Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +6DEB3 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +6DEB7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6DEB9 Length 000B (11) │ │ │ │ +6DEBB Version 01 (1) │ │ │ │ +6DEBC UID Size 04 (4) │ │ │ │ +6DEBD UID 00000000 (0) │ │ │ │ +6DEC1 GID Size 04 (4) │ │ │ │ +6DEC2 GID 00000000 (0) │ │ │ │ +6DEC6 PAYLOAD │ │ │ │ + │ │ │ │ +6EDC2 LOCAL HEADER #62 04034B50 (67324752) │ │ │ │ +6EDC6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +6EDC7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +6EDC8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +6EDCA Compression Method 0008 (8) 'Deflated' │ │ │ │ +6EDCC Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +6EDD0 CRC C6BC95DE (3334247902) │ │ │ │ +6EDD4 Compressed Size 00001ABC (6844) │ │ │ │ +6EDD8 Uncompressed Size 00005F39 (24377) │ │ │ │ +6EDDC Filename Length 0017 (23) │ │ │ │ +6EDDE Extra Length 001C (28) │ │ │ │ +6EDE0 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x6EDE0: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +6EDF7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +6EDF9 Length 0009 (9) │ │ │ │ +6EDFB Flags 03 (3) 'Modification Access' │ │ │ │ +6EDFC Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +6EE00 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +6EE04 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +6EE06 Length 000B (11) │ │ │ │ +6EE08 Version 01 (1) │ │ │ │ +6EE09 UID Size 04 (4) │ │ │ │ +6EE0A UID 00000000 (0) │ │ │ │ +6EE0E GID Size 04 (4) │ │ │ │ +6EE0F GID 00000000 (0) │ │ │ │ +6EE13 PAYLOAD │ │ │ │ + │ │ │ │ +708CF LOCAL HEADER #63 04034B50 (67324752) │ │ │ │ +708D3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +708D4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +708D5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +708D7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +708D9 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +708DD CRC 3DD8544C (1037587532) │ │ │ │ +708E1 Compressed Size 00000ED1 (3793) │ │ │ │ +708E5 Uncompressed Size 000038DE (14558) │ │ │ │ +708E9 Filename Length 0023 (35) │ │ │ │ +708EB Extra Length 001C (28) │ │ │ │ +708ED Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x708ED: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +70910 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +70912 Length 0009 (9) │ │ │ │ +70914 Flags 03 (3) 'Modification Access' │ │ │ │ +70915 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +70919 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +7091D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +7091F Length 000B (11) │ │ │ │ +70921 Version 01 (1) │ │ │ │ +70922 UID Size 04 (4) │ │ │ │ +70923 UID 00000000 (0) │ │ │ │ +70927 GID Size 04 (4) │ │ │ │ +70928 GID 00000000 (0) │ │ │ │ +7092C PAYLOAD │ │ │ │ + │ │ │ │ +717FD LOCAL HEADER #64 04034B50 (67324752) │ │ │ │ +71801 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +71802 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +71803 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +71805 Compression Method 0008 (8) 'Deflated' │ │ │ │ +71807 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +7180B CRC 2DB7929F (767005343) │ │ │ │ +7180F Compressed Size 00000113 (275) │ │ │ │ +71813 Uncompressed Size 000001F3 (499) │ │ │ │ +71817 Filename Length 001B (27) │ │ │ │ +71819 Extra Length 001C (28) │ │ │ │ +7181B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x7181B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +71836 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +71838 Length 0009 (9) │ │ │ │ +7183A Flags 03 (3) 'Modification Access' │ │ │ │ +7183B Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +7183F Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +71843 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +71845 Length 000B (11) │ │ │ │ +71847 Version 01 (1) │ │ │ │ +71848 UID Size 04 (4) │ │ │ │ +71849 UID 00000000 (0) │ │ │ │ +7184D GID Size 04 (4) │ │ │ │ +7184E GID 00000000 (0) │ │ │ │ +71852 PAYLOAD │ │ │ │ + │ │ │ │ +71965 LOCAL HEADER #65 04034B50 (67324752) │ │ │ │ +71969 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +7196A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +7196B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +7196D Compression Method 0008 (8) 'Deflated' │ │ │ │ +7196F Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +71973 CRC D8D75A4A (3637992010) │ │ │ │ +71977 Compressed Size 0000189E (6302) │ │ │ │ +7197B Uncompressed Size 00008F47 (36679) │ │ │ │ +7197F Filename Length 001D (29) │ │ │ │ +71981 Extra Length 001C (28) │ │ │ │ +71983 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x71983: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +719A0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +719A2 Length 0009 (9) │ │ │ │ +719A4 Flags 03 (3) 'Modification Access' │ │ │ │ +719A5 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +719A9 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +719AD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +719AF Length 000B (11) │ │ │ │ +719B1 Version 01 (1) │ │ │ │ +719B2 UID Size 04 (4) │ │ │ │ +719B3 UID 00000000 (0) │ │ │ │ +719B7 GID Size 04 (4) │ │ │ │ +719B8 GID 00000000 (0) │ │ │ │ +719BC PAYLOAD │ │ │ │ + │ │ │ │ +7325A LOCAL HEADER #66 04034B50 (67324752) │ │ │ │ +7325E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +7325F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +73260 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +73262 Compression Method 0008 (8) 'Deflated' │ │ │ │ +73264 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +73268 CRC 516C0F5D (1366036317) │ │ │ │ +7326C Compressed Size 0000164A (5706) │ │ │ │ +73270 Uncompressed Size 00003A9C (15004) │ │ │ │ +73274 Filename Length 0015 (21) │ │ │ │ +73276 Extra Length 001C (28) │ │ │ │ +73278 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x73278: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +7328D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +7328F Length 0009 (9) │ │ │ │ +73291 Flags 03 (3) 'Modification Access' │ │ │ │ +73292 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +73296 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +7329A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +7329C Length 000B (11) │ │ │ │ +7329E Version 01 (1) │ │ │ │ +7329F UID Size 04 (4) │ │ │ │ +732A0 UID 00000000 (0) │ │ │ │ +732A4 GID Size 04 (4) │ │ │ │ +732A5 GID 00000000 (0) │ │ │ │ +732A9 PAYLOAD │ │ │ │ + │ │ │ │ +748F3 LOCAL HEADER #67 04034B50 (67324752) │ │ │ │ +748F7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +748F8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +748F9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +748FB Compression Method 0008 (8) 'Deflated' │ │ │ │ +748FD Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +74901 CRC 0F59A4E7 (257533159) │ │ │ │ +74905 Compressed Size 00003B48 (15176) │ │ │ │ +74909 Uncompressed Size 00011C7F (72831) │ │ │ │ +7490D Filename Length 0016 (22) │ │ │ │ +7490F Extra Length 001C (28) │ │ │ │ +74911 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x74911: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +74927 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +74929 Length 0009 (9) │ │ │ │ +7492B Flags 03 (3) 'Modification Access' │ │ │ │ +7492C Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +74930 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +74934 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +74936 Length 000B (11) │ │ │ │ +74938 Version 01 (1) │ │ │ │ +74939 UID Size 04 (4) │ │ │ │ +7493A UID 00000000 (0) │ │ │ │ +7493E GID Size 04 (4) │ │ │ │ +7493F GID 00000000 (0) │ │ │ │ +74943 PAYLOAD │ │ │ │ + │ │ │ │ +7848B LOCAL HEADER #68 04034B50 (67324752) │ │ │ │ +7848F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +78490 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +78491 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +78493 Compression Method 0008 (8) 'Deflated' │ │ │ │ +78495 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +78499 CRC 00851BDD (8723421) │ │ │ │ +7849D Compressed Size 00003EF2 (16114) │ │ │ │ +784A1 Uncompressed Size 0001CBD3 (117715) │ │ │ │ +784A5 Filename Length 0019 (25) │ │ │ │ +784A7 Extra Length 001C (28) │ │ │ │ +784A9 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x784A9: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +784C2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +784C4 Length 0009 (9) │ │ │ │ +784C6 Flags 03 (3) 'Modification Access' │ │ │ │ +784C7 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +784CB Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +784CF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +784D1 Length 000B (11) │ │ │ │ +784D3 Version 01 (1) │ │ │ │ +784D4 UID Size 04 (4) │ │ │ │ +784D5 UID 00000000 (0) │ │ │ │ +784D9 GID Size 04 (4) │ │ │ │ +784DA GID 00000000 (0) │ │ │ │ +784DE PAYLOAD │ │ │ │ + │ │ │ │ +7C3D0 LOCAL HEADER #69 04034B50 (67324752) │ │ │ │ +7C3D4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +7C3D5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +7C3D6 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +7C3D8 Compression Method 0008 (8) 'Deflated' │ │ │ │ +7C3DA Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +7C3DE CRC 66C2C5FB (1724040699) │ │ │ │ +7C3E2 Compressed Size 0000083A (2106) │ │ │ │ +7C3E6 Uncompressed Size 00003384 (13188) │ │ │ │ +7C3EA Filename Length 0011 (17) │ │ │ │ +7C3EC Extra Length 001C (28) │ │ │ │ +7C3EE Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x7C3EE: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +7C3FF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +7C401 Length 0009 (9) │ │ │ │ +7C403 Flags 03 (3) 'Modification Access' │ │ │ │ +7C404 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +7C408 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +7C40C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +7C40E Length 000B (11) │ │ │ │ +7C410 Version 01 (1) │ │ │ │ +7C411 UID Size 04 (4) │ │ │ │ +7C412 UID 00000000 (0) │ │ │ │ +7C416 GID Size 04 (4) │ │ │ │ +7C417 GID 00000000 (0) │ │ │ │ +7C41B PAYLOAD │ │ │ │ + │ │ │ │ +7CC55 LOCAL HEADER #70 04034B50 (67324752) │ │ │ │ +7CC59 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +7CC5A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +7CC5B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +7CC5D Compression Method 0008 (8) 'Deflated' │ │ │ │ +7CC5F Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +7CC63 CRC ED0886E6 (3976759014) │ │ │ │ +7CC67 Compressed Size 000051B2 (20914) │ │ │ │ +7CC6B Uncompressed Size 0001FBE0 (130016) │ │ │ │ +7CC6F Filename Length 0015 (21) │ │ │ │ +7CC71 Extra Length 001C (28) │ │ │ │ +7CC73 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x7CC73: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +7CC88 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +7CC8A Length 0009 (9) │ │ │ │ +7CC8C Flags 03 (3) 'Modification Access' │ │ │ │ +7CC8D Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +7CC91 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +7CC95 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +7CC97 Length 000B (11) │ │ │ │ +7CC99 Version 01 (1) │ │ │ │ +7CC9A UID Size 04 (4) │ │ │ │ +7CC9B UID 00000000 (0) │ │ │ │ +7CC9F GID Size 04 (4) │ │ │ │ +7CCA0 GID 00000000 (0) │ │ │ │ +7CCA4 PAYLOAD │ │ │ │ + │ │ │ │ +81E56 LOCAL HEADER #71 04034B50 (67324752) │ │ │ │ +81E5A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +81E5B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +81E5C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +81E5E Compression Method 0008 (8) 'Deflated' │ │ │ │ +81E60 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +81E64 CRC EB0AC408 (3943351304) │ │ │ │ +81E68 Compressed Size 00001B1B (6939) │ │ │ │ +81E6C Uncompressed Size 00008213 (33299) │ │ │ │ +81E70 Filename Length 0019 (25) │ │ │ │ +81E72 Extra Length 001C (28) │ │ │ │ +81E74 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x81E74: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +81E8D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +81E8F Length 0009 (9) │ │ │ │ +81E91 Flags 03 (3) 'Modification Access' │ │ │ │ +81E92 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +81E96 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +81E9A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +81E9C Length 000B (11) │ │ │ │ +81E9E Version 01 (1) │ │ │ │ +81E9F UID Size 04 (4) │ │ │ │ +81EA0 UID 00000000 (0) │ │ │ │ +81EA4 GID Size 04 (4) │ │ │ │ +81EA5 GID 00000000 (0) │ │ │ │ +81EA9 PAYLOAD │ │ │ │ + │ │ │ │ +839C4 LOCAL HEADER #72 04034B50 (67324752) │ │ │ │ +839C8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +839C9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +839CA General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +839CC Compression Method 0008 (8) 'Deflated' │ │ │ │ +839CE Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +839D2 CRC 8690A50D (2257626381) │ │ │ │ +839D6 Compressed Size 00000D96 (3478) │ │ │ │ +839DA Uncompressed Size 00002EA0 (11936) │ │ │ │ +839DE Filename Length 0018 (24) │ │ │ │ +839E0 Extra Length 001C (28) │ │ │ │ +839E2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x839E2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +839FA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +839FC Length 0009 (9) │ │ │ │ +839FE Flags 03 (3) 'Modification Access' │ │ │ │ +839FF Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +83A03 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +83A07 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +83A09 Length 000B (11) │ │ │ │ +83A0B Version 01 (1) │ │ │ │ +83A0C UID Size 04 (4) │ │ │ │ +83A0D UID 00000000 (0) │ │ │ │ +83A11 GID Size 04 (4) │ │ │ │ +83A12 GID 00000000 (0) │ │ │ │ +83A16 PAYLOAD │ │ │ │ + │ │ │ │ +847AC LOCAL HEADER #73 04034B50 (67324752) │ │ │ │ +847B0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +847B1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +847B2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +847B4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +847B6 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +847BA CRC 037E3A9E (58604190) │ │ │ │ +847BE Compressed Size 000001E1 (481) │ │ │ │ +847C2 Uncompressed Size 00000324 (804) │ │ │ │ +847C6 Filename Length 0011 (17) │ │ │ │ +847C8 Extra Length 001C (28) │ │ │ │ +847CA Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x847CA: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +847DB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +847DD Length 0009 (9) │ │ │ │ +847DF Flags 03 (3) 'Modification Access' │ │ │ │ +847E0 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +847E4 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +847E8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +847EA Length 000B (11) │ │ │ │ +847EC Version 01 (1) │ │ │ │ +847ED UID Size 04 (4) │ │ │ │ +847EE UID 00000000 (0) │ │ │ │ +847F2 GID Size 04 (4) │ │ │ │ +847F3 GID 00000000 (0) │ │ │ │ +847F7 PAYLOAD │ │ │ │ + │ │ │ │ +849D8 LOCAL HEADER #74 04034B50 (67324752) │ │ │ │ +849DC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +849DD Extract OS 00 (0) 'MS-DOS' │ │ │ │ +849DE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +849E0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +849E2 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +849E6 CRC 55187787 (1427666823) │ │ │ │ +849EA Compressed Size 000006C3 (1731) │ │ │ │ +849EE Uncompressed Size 0000143A (5178) │ │ │ │ +849F2 Filename Length 0019 (25) │ │ │ │ +849F4 Extra Length 001C (28) │ │ │ │ +849F6 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x849F6: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +84A0F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +84A11 Length 0009 (9) │ │ │ │ +84A13 Flags 03 (3) 'Modification Access' │ │ │ │ +84A14 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +84A18 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +84A1C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +84A1E Length 000B (11) │ │ │ │ +84A20 Version 01 (1) │ │ │ │ +84A21 UID Size 04 (4) │ │ │ │ +84A22 UID 00000000 (0) │ │ │ │ +84A26 GID Size 04 (4) │ │ │ │ +84A27 GID 00000000 (0) │ │ │ │ +84A2B PAYLOAD │ │ │ │ + │ │ │ │ +850EE LOCAL HEADER #75 04034B50 (67324752) │ │ │ │ +850F2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +850F3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +850F4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +850F6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +850F8 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +850FC CRC 86D4A529 (2262082857) │ │ │ │ +85100 Compressed Size 00001EA6 (7846) │ │ │ │ +85104 Uncompressed Size 0000CA55 (51797) │ │ │ │ +85108 Filename Length 0018 (24) │ │ │ │ +8510A Extra Length 001C (28) │ │ │ │ +8510C Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8510C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +85124 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +85126 Length 0009 (9) │ │ │ │ +85128 Flags 03 (3) 'Modification Access' │ │ │ │ +85129 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +8512D Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +85131 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +85133 Length 000B (11) │ │ │ │ +85135 Version 01 (1) │ │ │ │ +85136 UID Size 04 (4) │ │ │ │ +85137 UID 00000000 (0) │ │ │ │ +8513B GID Size 04 (4) │ │ │ │ +8513C GID 00000000 (0) │ │ │ │ +85140 PAYLOAD │ │ │ │ + │ │ │ │ +86FE6 LOCAL HEADER #76 04034B50 (67324752) │ │ │ │ +86FEA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +86FEB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +86FEC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +86FEE Compression Method 0008 (8) 'Deflated' │ │ │ │ +86FF0 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +86FF4 CRC 077CFA4B (125631051) │ │ │ │ +86FF8 Compressed Size 00001C07 (7175) │ │ │ │ +86FFC Uncompressed Size 0000C5BE (50622) │ │ │ │ +87000 Filename Length 0012 (18) │ │ │ │ +87002 Extra Length 001C (28) │ │ │ │ +87004 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x87004: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +87016 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +87018 Length 0009 (9) │ │ │ │ +8701A Flags 03 (3) 'Modification Access' │ │ │ │ +8701B Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +8701F Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +87023 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +87025 Length 000B (11) │ │ │ │ +87027 Version 01 (1) │ │ │ │ +87028 UID Size 04 (4) │ │ │ │ +87029 UID 00000000 (0) │ │ │ │ +8702D GID Size 04 (4) │ │ │ │ +8702E GID 00000000 (0) │ │ │ │ +87032 PAYLOAD │ │ │ │ + │ │ │ │ +88C39 LOCAL HEADER #77 04034B50 (67324752) │ │ │ │ +88C3D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +88C3E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +88C3F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +88C41 Compression Method 0008 (8) 'Deflated' │ │ │ │ +88C43 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +88C47 CRC 72524D81 (1917996417) │ │ │ │ +88C4B Compressed Size 00001E16 (7702) │ │ │ │ +88C4F Uncompressed Size 000087D8 (34776) │ │ │ │ +88C53 Filename Length 0016 (22) │ │ │ │ +88C55 Extra Length 001C (28) │ │ │ │ +88C57 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x88C57: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +88C6D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +88C6F Length 0009 (9) │ │ │ │ +88C71 Flags 03 (3) 'Modification Access' │ │ │ │ +88C72 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +88C76 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +88C7A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +88C7C Length 000B (11) │ │ │ │ +88C7E Version 01 (1) │ │ │ │ +88C7F UID Size 04 (4) │ │ │ │ +88C80 UID 00000000 (0) │ │ │ │ +88C84 GID Size 04 (4) │ │ │ │ +88C85 GID 00000000 (0) │ │ │ │ +88C89 PAYLOAD │ │ │ │ + │ │ │ │ +8AA9F LOCAL HEADER #78 04034B50 (67324752) │ │ │ │ +8AAA3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +8AAA4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8AAA5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8AAA7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +8AAA9 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +8AAAD CRC 4A3E1EE6 (1245585126) │ │ │ │ +8AAB1 Compressed Size 000029AC (10668) │ │ │ │ +8AAB5 Uncompressed Size 0000D039 (53305) │ │ │ │ +8AAB9 Filename Length 001A (26) │ │ │ │ +8AABB Extra Length 001C (28) │ │ │ │ +8AABD Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8AABD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +8AAD7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +8AAD9 Length 0009 (9) │ │ │ │ +8AADB Flags 03 (3) 'Modification Access' │ │ │ │ +8AADC Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +8AAE0 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +8AAE4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8AAE6 Length 000B (11) │ │ │ │ +8AAE8 Version 01 (1) │ │ │ │ +8AAE9 UID Size 04 (4) │ │ │ │ +8AAEA UID 00000000 (0) │ │ │ │ +8AAEE GID Size 04 (4) │ │ │ │ +8AAEF GID 00000000 (0) │ │ │ │ +8AAF3 PAYLOAD │ │ │ │ + │ │ │ │ +8D49F LOCAL HEADER #79 04034B50 (67324752) │ │ │ │ +8D4A3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +8D4A4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8D4A5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8D4A7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +8D4A9 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +8D4AD CRC C2361EE0 (3258326752) │ │ │ │ +8D4B1 Compressed Size 000009AE (2478) │ │ │ │ +8D4B5 Uncompressed Size 00001DB7 (7607) │ │ │ │ +8D4B9 Filename Length 0018 (24) │ │ │ │ +8D4BB Extra Length 001C (28) │ │ │ │ +8D4BD Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8D4BD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +8D4D5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +8D4D7 Length 0009 (9) │ │ │ │ +8D4D9 Flags 03 (3) 'Modification Access' │ │ │ │ +8D4DA Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +8D4DE Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +8D4E2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8D4E4 Length 000B (11) │ │ │ │ +8D4E6 Version 01 (1) │ │ │ │ +8D4E7 UID Size 04 (4) │ │ │ │ +8D4E8 UID 00000000 (0) │ │ │ │ +8D4EC GID Size 04 (4) │ │ │ │ +8D4ED GID 00000000 (0) │ │ │ │ +8D4F1 PAYLOAD │ │ │ │ + │ │ │ │ +8DE9F LOCAL HEADER #80 04034B50 (67324752) │ │ │ │ +8DEA3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +8DEA4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8DEA5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8DEA7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +8DEA9 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +8DEAD CRC F5E2129F (4125233823) │ │ │ │ +8DEB1 Compressed Size 000016BC (5820) │ │ │ │ +8DEB5 Uncompressed Size 000016CD (5837) │ │ │ │ +8DEB9 Filename Length 0015 (21) │ │ │ │ +8DEBB Extra Length 001C (28) │ │ │ │ +8DEBD Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8DEBD: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +8DED2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +8DED4 Length 0009 (9) │ │ │ │ +8DED6 Flags 03 (3) 'Modification Access' │ │ │ │ +8DED7 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +8DEDB Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +8DEDF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8DEE1 Length 000B (11) │ │ │ │ +8DEE3 Version 01 (1) │ │ │ │ +8DEE4 UID Size 04 (4) │ │ │ │ +8DEE5 UID 00000000 (0) │ │ │ │ +8DEE9 GID Size 04 (4) │ │ │ │ +8DEEA GID 00000000 (0) │ │ │ │ +8DEEE PAYLOAD │ │ │ │ + │ │ │ │ +8F5AA LOCAL HEADER #81 04034B50 (67324752) │ │ │ │ +8F5AE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +8F5AF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +8F5B0 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +8F5B2 Compression Method 0008 (8) 'Deflated' │ │ │ │ +8F5B4 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +8F5B8 CRC F5E2129F (4125233823) │ │ │ │ +8F5BC Compressed Size 000016BC (5820) │ │ │ │ +8F5C0 Uncompressed Size 000016CD (5837) │ │ │ │ +8F5C4 Filename Length 001C (28) │ │ │ │ +8F5C6 Extra Length 001C (28) │ │ │ │ +8F5C8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x8F5C8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +8F5E4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +8F5E6 Length 0009 (9) │ │ │ │ +8F5E8 Flags 03 (3) 'Modification Access' │ │ │ │ +8F5E9 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +8F5ED Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +8F5F1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +8F5F3 Length 000B (11) │ │ │ │ +8F5F5 Version 01 (1) │ │ │ │ +8F5F6 UID Size 04 (4) │ │ │ │ +8F5F7 UID 00000000 (0) │ │ │ │ +8F5FB GID Size 04 (4) │ │ │ │ +8F5FC GID 00000000 (0) │ │ │ │ +8F600 PAYLOAD │ │ │ │ + │ │ │ │ +90CBC LOCAL HEADER #82 04034B50 (67324752) │ │ │ │ +90CC0 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +90CC1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +90CC2 General Purpose Flag 0000 (0) │ │ │ │ +90CC4 Compression Method 0000 (0) 'Stored' │ │ │ │ +90CC6 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +90CCA CRC FC95F24B (4237685323) │ │ │ │ +90CCE Compressed Size 00001B84 (7044) │ │ │ │ +90CD2 Uncompressed Size 00001B84 (7044) │ │ │ │ +90CD6 Filename Length 0016 (22) │ │ │ │ +90CD8 Extra Length 001C (28) │ │ │ │ +90CDA Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x90CDA: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +90CF0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +90CF2 Length 0009 (9) │ │ │ │ +90CF4 Flags 03 (3) 'Modification Access' │ │ │ │ +90CF5 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +90CF9 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +90CFD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +90CFF Length 000B (11) │ │ │ │ +90D01 Version 01 (1) │ │ │ │ +90D02 UID Size 04 (4) │ │ │ │ +90D03 UID 00000000 (0) │ │ │ │ +90D07 GID Size 04 (4) │ │ │ │ +90D08 GID 00000000 (0) │ │ │ │ +90D0C PAYLOAD │ │ │ │ + │ │ │ │ +92890 LOCAL HEADER #83 04034B50 (67324752) │ │ │ │ +92894 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +92895 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +92896 General Purpose Flag 0000 (0) │ │ │ │ +92898 Compression Method 0000 (0) 'Stored' │ │ │ │ +9289A Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9289E CRC D0D71F86 (3503759238) │ │ │ │ +928A2 Compressed Size 00000B7B (2939) │ │ │ │ +928A6 Uncompressed Size 00000B7B (2939) │ │ │ │ +928AA Filename Length 0016 (22) │ │ │ │ +928AC Extra Length 001C (28) │ │ │ │ +928AE Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x928AE: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +928C4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +928C6 Length 0009 (9) │ │ │ │ +928C8 Flags 03 (3) 'Modification Access' │ │ │ │ +928C9 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +928CD Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +928D1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +928D3 Length 000B (11) │ │ │ │ +928D5 Version 01 (1) │ │ │ │ +928D6 UID Size 04 (4) │ │ │ │ +928D7 UID 00000000 (0) │ │ │ │ +928DB GID Size 04 (4) │ │ │ │ +928DC GID 00000000 (0) │ │ │ │ +928E0 PAYLOAD │ │ │ │ + │ │ │ │ +9345B LOCAL HEADER #84 04034B50 (67324752) │ │ │ │ +9345F Extract Zip Spec 0A (10) '1.0' │ │ │ │ +93460 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +93461 General Purpose Flag 0000 (0) │ │ │ │ +93463 Compression Method 0000 (0) 'Stored' │ │ │ │ +93465 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +93469 CRC FFF9C4D2 (4294558930) │ │ │ │ +9346D Compressed Size 0000138F (5007) │ │ │ │ +93471 Uncompressed Size 0000138F (5007) │ │ │ │ +93475 Filename Length 0016 (22) │ │ │ │ +93477 Extra Length 001C (28) │ │ │ │ +93479 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x93479: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9348F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +93491 Length 0009 (9) │ │ │ │ +93493 Flags 03 (3) 'Modification Access' │ │ │ │ +93494 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +93498 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9349C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9349E Length 000B (11) │ │ │ │ +934A0 Version 01 (1) │ │ │ │ +934A1 UID Size 04 (4) │ │ │ │ +934A2 UID 00000000 (0) │ │ │ │ +934A6 GID Size 04 (4) │ │ │ │ +934A7 GID 00000000 (0) │ │ │ │ +934AB PAYLOAD │ │ │ │ + │ │ │ │ +9483A LOCAL HEADER #85 04034B50 (67324752) │ │ │ │ +9483E Extract Zip Spec 0A (10) '1.0' │ │ │ │ +9483F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +94840 General Purpose Flag 0000 (0) │ │ │ │ +94842 Compression Method 0000 (0) 'Stored' │ │ │ │ +94844 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +94848 CRC A1037E8E (2701360782) │ │ │ │ +9484C Compressed Size 0000145E (5214) │ │ │ │ +94850 Uncompressed Size 0000145E (5214) │ │ │ │ +94854 Filename Length 0016 (22) │ │ │ │ +94856 Extra Length 001C (28) │ │ │ │ +94858 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x94858: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9486E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +94870 Length 0009 (9) │ │ │ │ +94872 Flags 03 (3) 'Modification Access' │ │ │ │ +94873 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +94877 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9487B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9487D Length 000B (11) │ │ │ │ +9487F Version 01 (1) │ │ │ │ +94880 UID Size 04 (4) │ │ │ │ +94881 UID 00000000 (0) │ │ │ │ +94885 GID Size 04 (4) │ │ │ │ +94886 GID 00000000 (0) │ │ │ │ +9488A PAYLOAD │ │ │ │ + │ │ │ │ +95CE8 LOCAL HEADER #86 04034B50 (67324752) │ │ │ │ +95CEC Extract Zip Spec 0A (10) '1.0' │ │ │ │ +95CED Extract OS 00 (0) 'MS-DOS' │ │ │ │ +95CEE General Purpose Flag 0000 (0) │ │ │ │ +95CF0 Compression Method 0000 (0) 'Stored' │ │ │ │ +95CF2 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +95CF6 CRC 5E9E64F1 (1587438833) │ │ │ │ +95CFA Compressed Size 000008EC (2284) │ │ │ │ +95CFE Uncompressed Size 000008EC (2284) │ │ │ │ +95D02 Filename Length 0016 (22) │ │ │ │ +95D04 Extra Length 001C (28) │ │ │ │ +95D06 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x95D06: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +95D1C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +95D1E Length 0009 (9) │ │ │ │ +95D20 Flags 03 (3) 'Modification Access' │ │ │ │ +95D21 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +95D25 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +95D29 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +95D2B Length 000B (11) │ │ │ │ +95D2D Version 01 (1) │ │ │ │ +95D2E UID Size 04 (4) │ │ │ │ +95D2F UID 00000000 (0) │ │ │ │ +95D33 GID Size 04 (4) │ │ │ │ +95D34 GID 00000000 (0) │ │ │ │ +95D38 PAYLOAD │ │ │ │ + │ │ │ │ +96624 LOCAL HEADER #87 04034B50 (67324752) │ │ │ │ +96628 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +96629 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9662A General Purpose Flag 0000 (0) │ │ │ │ +9662C Compression Method 0000 (0) 'Stored' │ │ │ │ +9662E Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +96632 CRC 42E340AB (1122189483) │ │ │ │ +96636 Compressed Size 00001F2E (7982) │ │ │ │ +9663A Uncompressed Size 00001F2E (7982) │ │ │ │ +9663E Filename Length 001E (30) │ │ │ │ +96640 Extra Length 001C (28) │ │ │ │ +96642 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x96642: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +96660 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +96662 Length 0009 (9) │ │ │ │ +96664 Flags 03 (3) 'Modification Access' │ │ │ │ +96665 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +96669 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9666D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9666F Length 000B (11) │ │ │ │ +96671 Version 01 (1) │ │ │ │ +96672 UID Size 04 (4) │ │ │ │ +96673 UID 00000000 (0) │ │ │ │ +96677 GID Size 04 (4) │ │ │ │ +96678 GID 00000000 (0) │ │ │ │ +9667C PAYLOAD │ │ │ │ + │ │ │ │ +985AA LOCAL HEADER #88 04034B50 (67324752) │ │ │ │ +985AE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +985AF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +985B0 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +985B2 Compression Method 0008 (8) 'Deflated' │ │ │ │ +985B4 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +985B8 CRC 652B4598 (1697334680) │ │ │ │ +985BC Compressed Size 000040A1 (16545) │ │ │ │ +985C0 Uncompressed Size 0001964C (104012) │ │ │ │ +985C4 Filename Length 001A (26) │ │ │ │ +985C6 Extra Length 001C (28) │ │ │ │ +985C8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x985C8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +985E2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +985E4 Length 0009 (9) │ │ │ │ +985E6 Flags 03 (3) 'Modification Access' │ │ │ │ +985E7 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +985EB Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +985EF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +985F1 Length 000B (11) │ │ │ │ +985F3 Version 01 (1) │ │ │ │ +985F4 UID Size 04 (4) │ │ │ │ +985F5 UID 00000000 (0) │ │ │ │ +985F9 GID Size 04 (4) │ │ │ │ +985FA GID 00000000 (0) │ │ │ │ +985FE PAYLOAD │ │ │ │ + │ │ │ │ +9C69F LOCAL HEADER #89 04034B50 (67324752) │ │ │ │ +9C6A3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9C6A4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9C6A5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9C6A7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9C6A9 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9C6AD CRC 54E1DD8B (1424088459) │ │ │ │ +9C6B1 Compressed Size 000029CB (10699) │ │ │ │ +9C6B5 Uncompressed Size 0000BAF5 (47861) │ │ │ │ +9C6B9 Filename Length 0018 (24) │ │ │ │ +9C6BB Extra Length 001C (28) │ │ │ │ +9C6BD Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9C6BD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9C6D5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9C6D7 Length 0009 (9) │ │ │ │ +9C6D9 Flags 03 (3) 'Modification Access' │ │ │ │ +9C6DA Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9C6DE Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9C6E2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9C6E4 Length 000B (11) │ │ │ │ +9C6E6 Version 01 (1) │ │ │ │ +9C6E7 UID Size 04 (4) │ │ │ │ +9C6E8 UID 00000000 (0) │ │ │ │ +9C6EC GID Size 04 (4) │ │ │ │ +9C6ED GID 00000000 (0) │ │ │ │ +9C6F1 PAYLOAD │ │ │ │ + │ │ │ │ +9F0BC LOCAL HEADER #90 04034B50 (67324752) │ │ │ │ +9F0C0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F0C1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F0C2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F0C4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F0C6 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F0CA CRC DCB3B516 (3702764822) │ │ │ │ +9F0CE Compressed Size 000000AE (174) │ │ │ │ +9F0D2 Uncompressed Size 000000FC (252) │ │ │ │ +9F0D6 Filename Length 0016 (22) │ │ │ │ +9F0D8 Extra Length 001C (28) │ │ │ │ +9F0DA Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F0DA: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F0F0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F0F2 Length 0009 (9) │ │ │ │ +9F0F4 Flags 03 (3) 'Modification Access' │ │ │ │ +9F0F5 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F0F9 Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F0FD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F0FF Length 000B (11) │ │ │ │ +9F101 Version 01 (1) │ │ │ │ +9F102 UID Size 04 (4) │ │ │ │ +9F103 UID 00000000 (0) │ │ │ │ +9F107 GID Size 04 (4) │ │ │ │ +9F108 GID 00000000 (0) │ │ │ │ +9F10C PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ │ │ │ │ -9F1B2 LOCAL HEADER #91 04034B50 (67324752) │ │ │ │ -9F1B6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F1B7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F1B8 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F1BA Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F1BC Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F1C0 CRC 58439733 (1480824627) │ │ │ │ -9F1C4 Compressed Size 00000077 (119) │ │ │ │ -9F1C8 Uncompressed Size 000000A2 (162) │ │ │ │ -9F1CC Filename Length 002D (45) │ │ │ │ -9F1CE Extra Length 001C (28) │ │ │ │ -9F1D0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F1D0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F1FD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F1FF Length 0009 (9) │ │ │ │ -9F201 Flags 03 (3) 'Modification Access' │ │ │ │ -9F202 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F206 Access Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F20A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F20C Length 000B (11) │ │ │ │ -9F20E Version 01 (1) │ │ │ │ -9F20F UID Size 04 (4) │ │ │ │ -9F210 UID 00000000 (0) │ │ │ │ -9F214 GID Size 04 (4) │ │ │ │ -9F215 GID 00000000 (0) │ │ │ │ -9F219 PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ - │ │ │ │ -9F290 CENTRAL HEADER #1 02014B50 (33639248) │ │ │ │ -9F294 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F295 Created OS 03 (3) 'Unix' │ │ │ │ -9F296 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -9F297 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F298 General Purpose Flag 0000 (0) │ │ │ │ -9F29A Compression Method 0000 (0) 'Stored' │ │ │ │ -9F29C Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F2A0 CRC 2CAB616F (749429103) │ │ │ │ -9F2A4 Compressed Size 00000014 (20) │ │ │ │ -9F2A8 Uncompressed Size 00000014 (20) │ │ │ │ -9F2AC Filename Length 0008 (8) │ │ │ │ -9F2AE Extra Length 0018 (24) │ │ │ │ -9F2B0 Comment Length 0000 (0) │ │ │ │ -9F2B2 Disk Start 0000 (0) │ │ │ │ -9F2B4 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F2B6 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F2BA Local Header Offset 00000000 (0) │ │ │ │ -9F2BE Filename 'XXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F2BE: Filename 'XXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F2C6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F2C8 Length 0005 (5) │ │ │ │ -9F2CA Flags 01 (1) 'Modification' │ │ │ │ -9F2CB Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F2CF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F2D1 Length 000B (11) │ │ │ │ -9F2D3 Version 01 (1) │ │ │ │ -9F2D4 UID Size 04 (4) │ │ │ │ -9F2D5 UID 00000000 (0) │ │ │ │ -9F2D9 GID Size 04 (4) │ │ │ │ -9F2DA GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F2DE CENTRAL HEADER #2 02014B50 (33639248) │ │ │ │ -9F2E2 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F2E3 Created OS 03 (3) 'Unix' │ │ │ │ -9F2E4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F2E5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F2E6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F2E8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F2EA Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F2EE CRC 7CFCE72A (2096949034) │ │ │ │ -9F2F2 Compressed Size 000015AD (5549) │ │ │ │ -9F2F6 Uncompressed Size 00004603 (17923) │ │ │ │ -9F2FA Filename Length 0014 (20) │ │ │ │ -9F2FC Extra Length 0018 (24) │ │ │ │ -9F2FE Comment Length 0000 (0) │ │ │ │ -9F300 Disk Start 0000 (0) │ │ │ │ -9F302 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F304 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F308 Local Header Offset 00000056 (86) │ │ │ │ -9F30C Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F30C: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F320 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F322 Length 0005 (5) │ │ │ │ -9F324 Flags 01 (1) 'Modification' │ │ │ │ -9F325 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F329 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F32B Length 000B (11) │ │ │ │ -9F32D Version 01 (1) │ │ │ │ -9F32E UID Size 04 (4) │ │ │ │ -9F32F UID 00000000 (0) │ │ │ │ -9F333 GID Size 04 (4) │ │ │ │ -9F334 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F338 CENTRAL HEADER #3 02014B50 (33639248) │ │ │ │ -9F33C Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F33D Created OS 03 (3) 'Unix' │ │ │ │ -9F33E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F33F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F340 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F342 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F344 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F348 CRC 741FDE60 (1948245600) │ │ │ │ -9F34C Compressed Size 000006D6 (1750) │ │ │ │ -9F350 Uncompressed Size 00001242 (4674) │ │ │ │ -9F354 Filename Length 0013 (19) │ │ │ │ -9F356 Extra Length 0018 (24) │ │ │ │ -9F358 Comment Length 0000 (0) │ │ │ │ -9F35A Disk Start 0000 (0) │ │ │ │ -9F35C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F35E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F362 Local Header Offset 00001651 (5713) │ │ │ │ -9F366 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F366: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F379 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F37B Length 0005 (5) │ │ │ │ -9F37D Flags 01 (1) 'Modification' │ │ │ │ -9F37E Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F382 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F384 Length 000B (11) │ │ │ │ -9F386 Version 01 (1) │ │ │ │ -9F387 UID Size 04 (4) │ │ │ │ -9F388 UID 00000000 (0) │ │ │ │ -9F38C GID Size 04 (4) │ │ │ │ -9F38D GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F391 CENTRAL HEADER #4 02014B50 (33639248) │ │ │ │ -9F395 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F396 Created OS 03 (3) 'Unix' │ │ │ │ -9F397 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F398 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F399 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F39B Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F39D Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F3A1 CRC 3CA07DB0 (1017150896) │ │ │ │ -9F3A5 Compressed Size 00002DA7 (11687) │ │ │ │ -9F3A9 Uncompressed Size 0000D0C2 (53442) │ │ │ │ -9F3AD Filename Length 0014 (20) │ │ │ │ -9F3AF Extra Length 0018 (24) │ │ │ │ -9F3B1 Comment Length 0000 (0) │ │ │ │ -9F3B3 Disk Start 0000 (0) │ │ │ │ -9F3B5 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F3B7 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F3BB Local Header Offset 00001D74 (7540) │ │ │ │ -9F3BF Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F3BF: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F3D3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F3D5 Length 0005 (5) │ │ │ │ -9F3D7 Flags 01 (1) 'Modification' │ │ │ │ -9F3D8 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F3DC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F3DE Length 000B (11) │ │ │ │ -9F3E0 Version 01 (1) │ │ │ │ -9F3E1 UID Size 04 (4) │ │ │ │ -9F3E2 UID 00000000 (0) │ │ │ │ -9F3E6 GID Size 04 (4) │ │ │ │ -9F3E7 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F3EB CENTRAL HEADER #5 02014B50 (33639248) │ │ │ │ -9F3EF Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F3F0 Created OS 03 (3) 'Unix' │ │ │ │ -9F3F1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F3F2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F3F3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F3F5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F3F7 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F3FB CRC AFEB34EA (2951427306) │ │ │ │ -9F3FF Compressed Size 000003F0 (1008) │ │ │ │ -9F403 Uncompressed Size 00000877 (2167) │ │ │ │ -9F407 Filename Length 0014 (20) │ │ │ │ -9F409 Extra Length 0018 (24) │ │ │ │ -9F40B Comment Length 0000 (0) │ │ │ │ -9F40D Disk Start 0000 (0) │ │ │ │ -9F40F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F411 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F415 Local Header Offset 00004B69 (19305) │ │ │ │ -9F419 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F419: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F42D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F42F Length 0005 (5) │ │ │ │ -9F431 Flags 01 (1) 'Modification' │ │ │ │ -9F432 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F436 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F438 Length 000B (11) │ │ │ │ -9F43A Version 01 (1) │ │ │ │ -9F43B UID Size 04 (4) │ │ │ │ -9F43C UID 00000000 (0) │ │ │ │ -9F440 GID Size 04 (4) │ │ │ │ -9F441 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F445 CENTRAL HEADER #6 02014B50 (33639248) │ │ │ │ -9F449 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F44A Created OS 03 (3) 'Unix' │ │ │ │ -9F44B Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F44C Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F44D General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F44F Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F451 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F455 CRC 21AF85A7 (565151143) │ │ │ │ -9F459 Compressed Size 000001AE (430) │ │ │ │ -9F45D Uncompressed Size 000002FE (766) │ │ │ │ -9F461 Filename Length 0011 (17) │ │ │ │ -9F463 Extra Length 0018 (24) │ │ │ │ -9F465 Comment Length 0000 (0) │ │ │ │ -9F467 Disk Start 0000 (0) │ │ │ │ -9F469 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F46B Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F46F Local Header Offset 00004FA7 (20391) │ │ │ │ -9F473 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F473: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F484 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F486 Length 0005 (5) │ │ │ │ -9F488 Flags 01 (1) 'Modification' │ │ │ │ -9F489 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F48D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F48F Length 000B (11) │ │ │ │ -9F491 Version 01 (1) │ │ │ │ -9F492 UID Size 04 (4) │ │ │ │ -9F493 UID 00000000 (0) │ │ │ │ -9F497 GID Size 04 (4) │ │ │ │ -9F498 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F49C CENTRAL HEADER #7 02014B50 (33639248) │ │ │ │ -9F4A0 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F4A1 Created OS 03 (3) 'Unix' │ │ │ │ -9F4A2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F4A3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F4A4 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F4A6 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F4A8 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F4AC CRC 0C5C4C93 (207375507) │ │ │ │ -9F4B0 Compressed Size 000020BD (8381) │ │ │ │ -9F4B4 Uncompressed Size 0000B4AC (46252) │ │ │ │ -9F4B8 Filename Length 001B (27) │ │ │ │ -9F4BA Extra Length 0018 (24) │ │ │ │ -9F4BC Comment Length 0000 (0) │ │ │ │ -9F4BE Disk Start 0000 (0) │ │ │ │ -9F4C0 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F4C2 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F4C6 Local Header Offset 000051A0 (20896) │ │ │ │ -9F4CA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F4CA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F4E5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F4E7 Length 0005 (5) │ │ │ │ -9F4E9 Flags 01 (1) 'Modification' │ │ │ │ -9F4EA Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F4EE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F4F0 Length 000B (11) │ │ │ │ -9F4F2 Version 01 (1) │ │ │ │ -9F4F3 UID Size 04 (4) │ │ │ │ -9F4F4 UID 00000000 (0) │ │ │ │ -9F4F8 GID Size 04 (4) │ │ │ │ -9F4F9 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F4FD CENTRAL HEADER #8 02014B50 (33639248) │ │ │ │ -9F501 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F502 Created OS 03 (3) 'Unix' │ │ │ │ -9F503 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F504 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F505 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F507 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F509 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F50D CRC 2D371F1E (758587166) │ │ │ │ -9F511 Compressed Size 00000E70 (3696) │ │ │ │ -9F515 Uncompressed Size 000030B3 (12467) │ │ │ │ -9F519 Filename Length 001D (29) │ │ │ │ -9F51B Extra Length 0018 (24) │ │ │ │ -9F51D Comment Length 0000 (0) │ │ │ │ -9F51F Disk Start 0000 (0) │ │ │ │ -9F521 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F523 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F527 Local Header Offset 000072B2 (29362) │ │ │ │ -9F52B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F52B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F548 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F54A Length 0005 (5) │ │ │ │ -9F54C Flags 01 (1) 'Modification' │ │ │ │ -9F54D Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F551 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F553 Length 000B (11) │ │ │ │ -9F555 Version 01 (1) │ │ │ │ -9F556 UID Size 04 (4) │ │ │ │ -9F557 UID 00000000 (0) │ │ │ │ -9F55B GID Size 04 (4) │ │ │ │ -9F55C GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F560 CENTRAL HEADER #9 02014B50 (33639248) │ │ │ │ -9F564 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F565 Created OS 03 (3) 'Unix' │ │ │ │ -9F566 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F567 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F568 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F56A Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F56C Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F570 CRC 7903D0CB (2030293195) │ │ │ │ -9F574 Compressed Size 00000973 (2419) │ │ │ │ -9F578 Uncompressed Size 00001CB3 (7347) │ │ │ │ -9F57C Filename Length 0019 (25) │ │ │ │ -9F57E Extra Length 0018 (24) │ │ │ │ -9F580 Comment Length 0000 (0) │ │ │ │ -9F582 Disk Start 0000 (0) │ │ │ │ -9F584 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F586 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F58A Local Header Offset 00008179 (33145) │ │ │ │ -9F58E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F58E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F5A7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F5A9 Length 0005 (5) │ │ │ │ -9F5AB Flags 01 (1) 'Modification' │ │ │ │ -9F5AC Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F5B0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F5B2 Length 000B (11) │ │ │ │ -9F5B4 Version 01 (1) │ │ │ │ -9F5B5 UID Size 04 (4) │ │ │ │ -9F5B6 UID 00000000 (0) │ │ │ │ -9F5BA GID Size 04 (4) │ │ │ │ -9F5BB GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F5BF CENTRAL HEADER #10 02014B50 (33639248) │ │ │ │ -9F5C3 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F5C4 Created OS 03 (3) 'Unix' │ │ │ │ -9F5C5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F5C6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F5C7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F5C9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F5CB Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F5CF CRC 68A2F74B (1755510603) │ │ │ │ -9F5D3 Compressed Size 00003864 (14436) │ │ │ │ -9F5D7 Uncompressed Size 0000F7B0 (63408) │ │ │ │ -9F5DB Filename Length 0015 (21) │ │ │ │ -9F5DD Extra Length 0018 (24) │ │ │ │ -9F5DF Comment Length 0000 (0) │ │ │ │ -9F5E1 Disk Start 0000 (0) │ │ │ │ -9F5E3 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F5E5 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F5E9 Local Header Offset 00008B3F (35647) │ │ │ │ -9F5ED Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F5ED: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F602 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F604 Length 0005 (5) │ │ │ │ -9F606 Flags 01 (1) 'Modification' │ │ │ │ -9F607 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F60B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F60D Length 000B (11) │ │ │ │ -9F60F Version 01 (1) │ │ │ │ -9F610 UID Size 04 (4) │ │ │ │ -9F611 UID 00000000 (0) │ │ │ │ -9F615 GID Size 04 (4) │ │ │ │ -9F616 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F61A CENTRAL HEADER #11 02014B50 (33639248) │ │ │ │ -9F61E Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F61F Created OS 03 (3) 'Unix' │ │ │ │ -9F620 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F621 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F622 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F624 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F626 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F62A CRC 3DCFFD78 (1037041016) │ │ │ │ -9F62E Compressed Size 0000A9BA (43450) │ │ │ │ -9F632 Uncompressed Size 0003D7D6 (251862) │ │ │ │ -9F636 Filename Length 0012 (18) │ │ │ │ -9F638 Extra Length 0018 (24) │ │ │ │ -9F63A Comment Length 0000 (0) │ │ │ │ -9F63C Disk Start 0000 (0) │ │ │ │ -9F63E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F640 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F644 Local Header Offset 0000C3F2 (50162) │ │ │ │ -9F648 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F648: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F65A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F65C Length 0005 (5) │ │ │ │ -9F65E Flags 01 (1) 'Modification' │ │ │ │ -9F65F Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F663 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F665 Length 000B (11) │ │ │ │ -9F667 Version 01 (1) │ │ │ │ -9F668 UID Size 04 (4) │ │ │ │ -9F669 UID 00000000 (0) │ │ │ │ -9F66D GID Size 04 (4) │ │ │ │ -9F66E GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F672 CENTRAL HEADER #12 02014B50 (33639248) │ │ │ │ -9F676 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F677 Created OS 03 (3) 'Unix' │ │ │ │ -9F678 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F679 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F67A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F67C Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F67E Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F682 CRC 409E09C8 (1084099016) │ │ │ │ -9F686 Compressed Size 00003B28 (15144) │ │ │ │ -9F68A Uncompressed Size 0001B412 (111634) │ │ │ │ -9F68E Filename Length 0015 (21) │ │ │ │ -9F690 Extra Length 0018 (24) │ │ │ │ -9F692 Comment Length 0000 (0) │ │ │ │ -9F694 Disk Start 0000 (0) │ │ │ │ -9F696 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F698 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F69C Local Header Offset 00016DF8 (93688) │ │ │ │ -9F6A0 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F6A0: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F6B5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F6B7 Length 0005 (5) │ │ │ │ -9F6B9 Flags 01 (1) 'Modification' │ │ │ │ -9F6BA Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F6BE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F6C0 Length 000B (11) │ │ │ │ -9F6C2 Version 01 (1) │ │ │ │ -9F6C3 UID Size 04 (4) │ │ │ │ -9F6C4 UID 00000000 (0) │ │ │ │ -9F6C8 GID Size 04 (4) │ │ │ │ -9F6C9 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F6CD CENTRAL HEADER #13 02014B50 (33639248) │ │ │ │ -9F6D1 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F6D2 Created OS 03 (3) 'Unix' │ │ │ │ -9F6D3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F6D4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F6D5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F6D7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F6D9 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F6DD CRC 6929B99A (1764342170) │ │ │ │ -9F6E1 Compressed Size 00009166 (37222) │ │ │ │ -9F6E5 Uncompressed Size 0003D921 (252193) │ │ │ │ -9F6E9 Filename Length 0014 (20) │ │ │ │ -9F6EB Extra Length 0018 (24) │ │ │ │ -9F6ED Comment Length 0000 (0) │ │ │ │ -9F6EF Disk Start 0000 (0) │ │ │ │ -9F6F1 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F6F3 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F6F7 Local Header Offset 0001A96F (108911) │ │ │ │ -9F6FB Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F6FB: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F70F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F711 Length 0005 (5) │ │ │ │ -9F713 Flags 01 (1) 'Modification' │ │ │ │ -9F714 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F718 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F71A Length 000B (11) │ │ │ │ -9F71C Version 01 (1) │ │ │ │ -9F71D UID Size 04 (4) │ │ │ │ -9F71E UID 00000000 (0) │ │ │ │ -9F722 GID Size 04 (4) │ │ │ │ -9F723 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F727 CENTRAL HEADER #14 02014B50 (33639248) │ │ │ │ -9F72B Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F72C Created OS 03 (3) 'Unix' │ │ │ │ -9F72D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F72E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F72F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F731 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F733 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F737 CRC D237ECE3 (3526880483) │ │ │ │ -9F73B Compressed Size 00002A76 (10870) │ │ │ │ -9F73F Uncompressed Size 00011520 (70944) │ │ │ │ -9F743 Filename Length 0016 (22) │ │ │ │ -9F745 Extra Length 0018 (24) │ │ │ │ -9F747 Comment Length 0000 (0) │ │ │ │ -9F749 Disk Start 0000 (0) │ │ │ │ -9F74B Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F74D Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F751 Local Header Offset 00023B23 (146211) │ │ │ │ -9F755 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F755: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F76B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F76D Length 0005 (5) │ │ │ │ -9F76F Flags 01 (1) 'Modification' │ │ │ │ -9F770 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F774 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F776 Length 000B (11) │ │ │ │ -9F778 Version 01 (1) │ │ │ │ -9F779 UID Size 04 (4) │ │ │ │ -9F77A UID 00000000 (0) │ │ │ │ -9F77E GID Size 04 (4) │ │ │ │ -9F77F GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F783 CENTRAL HEADER #15 02014B50 (33639248) │ │ │ │ -9F787 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F788 Created OS 03 (3) 'Unix' │ │ │ │ -9F789 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F78A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F78B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F78D Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F78F Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F793 CRC AAAA060E (2863269390) │ │ │ │ -9F797 Compressed Size 000014DF (5343) │ │ │ │ -9F79B Uncompressed Size 0000518E (20878) │ │ │ │ -9F79F Filename Length 001D (29) │ │ │ │ -9F7A1 Extra Length 0018 (24) │ │ │ │ -9F7A3 Comment Length 0000 (0) │ │ │ │ -9F7A5 Disk Start 0000 (0) │ │ │ │ -9F7A7 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F7A9 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F7AD Local Header Offset 000265E9 (157161) │ │ │ │ -9F7B1 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F7B1: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F7CE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F7D0 Length 0005 (5) │ │ │ │ -9F7D2 Flags 01 (1) 'Modification' │ │ │ │ -9F7D3 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F7D7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F7D9 Length 000B (11) │ │ │ │ -9F7DB Version 01 (1) │ │ │ │ -9F7DC UID Size 04 (4) │ │ │ │ -9F7DD UID 00000000 (0) │ │ │ │ -9F7E1 GID Size 04 (4) │ │ │ │ -9F7E2 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F7E6 CENTRAL HEADER #16 02014B50 (33639248) │ │ │ │ -9F7EA Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F7EB Created OS 03 (3) 'Unix' │ │ │ │ -9F7EC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F7ED Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F7EE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F7F0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F7F2 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F7F6 CRC 878C1C89 (2274106505) │ │ │ │ -9F7FA Compressed Size 000039CB (14795) │ │ │ │ -9F7FE Uncompressed Size 0000F03B (61499) │ │ │ │ -9F802 Filename Length 001C (28) │ │ │ │ -9F804 Extra Length 0018 (24) │ │ │ │ -9F806 Comment Length 0000 (0) │ │ │ │ -9F808 Disk Start 0000 (0) │ │ │ │ -9F80A Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F80C Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F810 Local Header Offset 00027B1F (162591) │ │ │ │ -9F814 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F814: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F830 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F832 Length 0005 (5) │ │ │ │ -9F834 Flags 01 (1) 'Modification' │ │ │ │ -9F835 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F839 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F83B Length 000B (11) │ │ │ │ -9F83D Version 01 (1) │ │ │ │ -9F83E UID Size 04 (4) │ │ │ │ -9F83F UID 00000000 (0) │ │ │ │ -9F843 GID Size 04 (4) │ │ │ │ -9F844 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F848 CENTRAL HEADER #17 02014B50 (33639248) │ │ │ │ -9F84C Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F84D Created OS 03 (3) 'Unix' │ │ │ │ -9F84E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F84F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F850 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F852 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F854 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F858 CRC 7A88ED58 (2055794008) │ │ │ │ -9F85C Compressed Size 000006A3 (1699) │ │ │ │ -9F860 Uncompressed Size 000011F5 (4597) │ │ │ │ -9F864 Filename Length 001C (28) │ │ │ │ -9F866 Extra Length 0018 (24) │ │ │ │ -9F868 Comment Length 0000 (0) │ │ │ │ -9F86A Disk Start 0000 (0) │ │ │ │ -9F86C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F86E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F872 Local Header Offset 0002B540 (177472) │ │ │ │ -9F876 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F876: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F892 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F894 Length 0005 (5) │ │ │ │ -9F896 Flags 01 (1) 'Modification' │ │ │ │ -9F897 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F89B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F89D Length 000B (11) │ │ │ │ -9F89F Version 01 (1) │ │ │ │ -9F8A0 UID Size 04 (4) │ │ │ │ -9F8A1 UID 00000000 (0) │ │ │ │ -9F8A5 GID Size 04 (4) │ │ │ │ -9F8A6 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F8AA CENTRAL HEADER #18 02014B50 (33639248) │ │ │ │ -9F8AE Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F8AF Created OS 03 (3) 'Unix' │ │ │ │ -9F8B0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F8B1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F8B2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F8B4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F8B6 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F8BA CRC 5F03D695 (1594087061) │ │ │ │ -9F8BE Compressed Size 0000107F (4223) │ │ │ │ -9F8C2 Uncompressed Size 00004BE9 (19433) │ │ │ │ -9F8C6 Filename Length 001B (27) │ │ │ │ -9F8C8 Extra Length 0018 (24) │ │ │ │ -9F8CA Comment Length 0000 (0) │ │ │ │ -9F8CC Disk Start 0000 (0) │ │ │ │ -9F8CE Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F8D0 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F8D4 Local Header Offset 0002BC39 (179257) │ │ │ │ -9F8D8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F8D8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F8F3 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F8F5 Length 0005 (5) │ │ │ │ -9F8F7 Flags 01 (1) 'Modification' │ │ │ │ -9F8F8 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F8FC Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F8FE Length 000B (11) │ │ │ │ -9F900 Version 01 (1) │ │ │ │ -9F901 UID Size 04 (4) │ │ │ │ -9F902 UID 00000000 (0) │ │ │ │ -9F906 GID Size 04 (4) │ │ │ │ -9F907 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F90B CENTRAL HEADER #19 02014B50 (33639248) │ │ │ │ -9F90F Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F910 Created OS 03 (3) 'Unix' │ │ │ │ -9F911 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F912 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F913 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F915 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F917 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F91B CRC 54D35332 (1423135538) │ │ │ │ -9F91F Compressed Size 000033AD (13229) │ │ │ │ -9F923 Uncompressed Size 0000BC95 (48277) │ │ │ │ -9F927 Filename Length 001D (29) │ │ │ │ -9F929 Extra Length 0018 (24) │ │ │ │ -9F92B Comment Length 0000 (0) │ │ │ │ -9F92D Disk Start 0000 (0) │ │ │ │ -9F92F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F931 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F935 Local Header Offset 0002CD0D (183565) │ │ │ │ -9F939 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F939: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F956 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F958 Length 0005 (5) │ │ │ │ -9F95A Flags 01 (1) 'Modification' │ │ │ │ -9F95B Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F95F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F961 Length 000B (11) │ │ │ │ -9F963 Version 01 (1) │ │ │ │ -9F964 UID Size 04 (4) │ │ │ │ -9F965 UID 00000000 (0) │ │ │ │ -9F969 GID Size 04 (4) │ │ │ │ -9F96A GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F96E CENTRAL HEADER #20 02014B50 (33639248) │ │ │ │ -9F972 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F973 Created OS 03 (3) 'Unix' │ │ │ │ -9F974 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F975 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F976 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F978 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F97A Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F97E CRC 355C0CEA (895225066) │ │ │ │ -9F982 Compressed Size 00000D6E (3438) │ │ │ │ -9F986 Uncompressed Size 0000388E (14478) │ │ │ │ -9F98A Filename Length 001D (29) │ │ │ │ -9F98C Extra Length 0018 (24) │ │ │ │ -9F98E Comment Length 0000 (0) │ │ │ │ -9F990 Disk Start 0000 (0) │ │ │ │ -9F992 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F994 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F998 Local Header Offset 00030111 (196881) │ │ │ │ -9F99C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F99C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9F9B9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9F9BB Length 0005 (5) │ │ │ │ -9F9BD Flags 01 (1) 'Modification' │ │ │ │ -9F9BE Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F9C2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9F9C4 Length 000B (11) │ │ │ │ -9F9C6 Version 01 (1) │ │ │ │ -9F9C7 UID Size 04 (4) │ │ │ │ -9F9C8 UID 00000000 (0) │ │ │ │ -9F9CC GID Size 04 (4) │ │ │ │ -9F9CD GID 00000000 (0) │ │ │ │ - │ │ │ │ -9F9D1 CENTRAL HEADER #21 02014B50 (33639248) │ │ │ │ -9F9D5 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9F9D6 Created OS 03 (3) 'Unix' │ │ │ │ -9F9D7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9F9D8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9F9D9 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9F9DB Compression Method 0008 (8) 'Deflated' │ │ │ │ -9F9DD Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9F9E1 CRC 99A8338F (2577937295) │ │ │ │ -9F9E5 Compressed Size 00001C7D (7293) │ │ │ │ -9F9E9 Uncompressed Size 0000C144 (49476) │ │ │ │ -9F9ED Filename Length 001A (26) │ │ │ │ -9F9EF Extra Length 0018 (24) │ │ │ │ -9F9F1 Comment Length 0000 (0) │ │ │ │ -9F9F3 Disk Start 0000 (0) │ │ │ │ -9F9F5 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9F9F7 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9F9FB Local Header Offset 00030ED6 (200406) │ │ │ │ -9F9FF Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9F9FF: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FA19 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FA1B Length 0005 (5) │ │ │ │ -9FA1D Flags 01 (1) 'Modification' │ │ │ │ -9FA1E Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FA22 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FA24 Length 000B (11) │ │ │ │ -9FA26 Version 01 (1) │ │ │ │ -9FA27 UID Size 04 (4) │ │ │ │ -9FA28 UID 00000000 (0) │ │ │ │ -9FA2C GID Size 04 (4) │ │ │ │ -9FA2D GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FA31 CENTRAL HEADER #22 02014B50 (33639248) │ │ │ │ -9FA35 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FA36 Created OS 03 (3) 'Unix' │ │ │ │ -9FA37 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FA38 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FA39 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FA3B Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FA3D Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FA41 CRC C41B95CB (3290142155) │ │ │ │ -9FA45 Compressed Size 000003A4 (932) │ │ │ │ -9FA49 Uncompressed Size 0000088F (2191) │ │ │ │ -9FA4D Filename Length 0012 (18) │ │ │ │ -9FA4F Extra Length 0018 (24) │ │ │ │ -9FA51 Comment Length 0000 (0) │ │ │ │ -9FA53 Disk Start 0000 (0) │ │ │ │ -9FA55 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FA57 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FA5B Local Header Offset 00032BA7 (207783) │ │ │ │ -9FA5F Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FA5F: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FA71 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FA73 Length 0005 (5) │ │ │ │ -9FA75 Flags 01 (1) 'Modification' │ │ │ │ -9FA76 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FA7A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FA7C Length 000B (11) │ │ │ │ -9FA7E Version 01 (1) │ │ │ │ -9FA7F UID Size 04 (4) │ │ │ │ -9FA80 UID 00000000 (0) │ │ │ │ -9FA84 GID Size 04 (4) │ │ │ │ -9FA85 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FA89 CENTRAL HEADER #23 02014B50 (33639248) │ │ │ │ -9FA8D Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FA8E Created OS 03 (3) 'Unix' │ │ │ │ -9FA8F Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FA90 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FA91 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FA93 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FA95 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FA99 CRC 40B68220 (1085702688) │ │ │ │ -9FA9D Compressed Size 000001D4 (468) │ │ │ │ -9FAA1 Uncompressed Size 00000312 (786) │ │ │ │ -9FAA5 Filename Length 0020 (32) │ │ │ │ -9FAA7 Extra Length 0018 (24) │ │ │ │ -9FAA9 Comment Length 0000 (0) │ │ │ │ -9FAAB Disk Start 0000 (0) │ │ │ │ -9FAAD Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FAAF Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FAB3 Local Header Offset 00032F97 (208791) │ │ │ │ -9FAB7 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FAB7: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FAD7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FAD9 Length 0005 (5) │ │ │ │ -9FADB Flags 01 (1) 'Modification' │ │ │ │ -9FADC Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FAE0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FAE2 Length 000B (11) │ │ │ │ -9FAE4 Version 01 (1) │ │ │ │ -9FAE5 UID Size 04 (4) │ │ │ │ -9FAE6 UID 00000000 (0) │ │ │ │ -9FAEA GID Size 04 (4) │ │ │ │ -9FAEB GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FAEF CENTRAL HEADER #24 02014B50 (33639248) │ │ │ │ -9FAF3 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FAF4 Created OS 03 (3) 'Unix' │ │ │ │ -9FAF5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FAF6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FAF7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FAF9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FAFB Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FAFF CRC 6B5C6C16 (1801219094) │ │ │ │ -9FB03 Compressed Size 000017AC (6060) │ │ │ │ -9FB07 Uncompressed Size 00009D19 (40217) │ │ │ │ -9FB0B Filename Length 001B (27) │ │ │ │ -9FB0D Extra Length 0018 (24) │ │ │ │ -9FB0F Comment Length 0000 (0) │ │ │ │ -9FB11 Disk Start 0000 (0) │ │ │ │ -9FB13 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FB15 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FB19 Local Header Offset 000331C5 (209349) │ │ │ │ -9FB1D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FB1D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FB38 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FB3A Length 0005 (5) │ │ │ │ -9FB3C Flags 01 (1) 'Modification' │ │ │ │ -9FB3D Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FB41 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FB43 Length 000B (11) │ │ │ │ -9FB45 Version 01 (1) │ │ │ │ -9FB46 UID Size 04 (4) │ │ │ │ -9FB47 UID 00000000 (0) │ │ │ │ -9FB4B GID Size 04 (4) │ │ │ │ -9FB4C GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FB50 CENTRAL HEADER #25 02014B50 (33639248) │ │ │ │ -9FB54 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FB55 Created OS 03 (3) 'Unix' │ │ │ │ -9FB56 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FB57 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FB58 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FB5A Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FB5C Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FB60 CRC 8B976C32 (2341956658) │ │ │ │ -9FB64 Compressed Size 00001374 (4980) │ │ │ │ -9FB68 Uncompressed Size 00003B67 (15207) │ │ │ │ -9FB6C Filename Length 0015 (21) │ │ │ │ -9FB6E Extra Length 0018 (24) │ │ │ │ -9FB70 Comment Length 0000 (0) │ │ │ │ -9FB72 Disk Start 0000 (0) │ │ │ │ -9FB74 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FB76 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FB7A Local Header Offset 000349C6 (215494) │ │ │ │ -9FB7E Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FB7E: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FB93 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FB95 Length 0005 (5) │ │ │ │ -9FB97 Flags 01 (1) 'Modification' │ │ │ │ -9FB98 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FB9C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FB9E Length 000B (11) │ │ │ │ -9FBA0 Version 01 (1) │ │ │ │ -9FBA1 UID Size 04 (4) │ │ │ │ -9FBA2 UID 00000000 (0) │ │ │ │ -9FBA6 GID Size 04 (4) │ │ │ │ -9FBA7 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FBAB CENTRAL HEADER #26 02014B50 (33639248) │ │ │ │ -9FBAF Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FBB0 Created OS 03 (3) 'Unix' │ │ │ │ -9FBB1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FBB2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FBB3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FBB5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FBB7 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FBBB CRC 2ACCA91C (718055708) │ │ │ │ -9FBBF Compressed Size 00000AD3 (2771) │ │ │ │ -9FBC3 Uncompressed Size 00002136 (8502) │ │ │ │ -9FBC7 Filename Length 0011 (17) │ │ │ │ -9FBC9 Extra Length 0018 (24) │ │ │ │ -9FBCB Comment Length 0000 (0) │ │ │ │ -9FBCD Disk Start 0000 (0) │ │ │ │ -9FBCF Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FBD1 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FBD5 Local Header Offset 00035D89 (220553) │ │ │ │ -9FBD9 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FBD9: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FBEA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FBEC Length 0005 (5) │ │ │ │ -9FBEE Flags 01 (1) 'Modification' │ │ │ │ -9FBEF Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FBF3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FBF5 Length 000B (11) │ │ │ │ -9FBF7 Version 01 (1) │ │ │ │ -9FBF8 UID Size 04 (4) │ │ │ │ -9FBF9 UID 00000000 (0) │ │ │ │ -9FBFD GID Size 04 (4) │ │ │ │ -9FBFE GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FC02 CENTRAL HEADER #27 02014B50 (33639248) │ │ │ │ -9FC06 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FC07 Created OS 03 (3) 'Unix' │ │ │ │ -9FC08 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FC09 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FC0A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FC0C Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FC0E Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FC12 CRC 4C2A379D (1277835165) │ │ │ │ -9FC16 Compressed Size 000003FE (1022) │ │ │ │ -9FC1A Uncompressed Size 00000F0D (3853) │ │ │ │ -9FC1E Filename Length 0014 (20) │ │ │ │ -9FC20 Extra Length 0018 (24) │ │ │ │ -9FC22 Comment Length 0000 (0) │ │ │ │ -9FC24 Disk Start 0000 (0) │ │ │ │ -9FC26 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FC28 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FC2C Local Header Offset 000368A7 (223399) │ │ │ │ -9FC30 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FC30: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FC44 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FC46 Length 0005 (5) │ │ │ │ -9FC48 Flags 01 (1) 'Modification' │ │ │ │ -9FC49 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FC4D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FC4F Length 000B (11) │ │ │ │ -9FC51 Version 01 (1) │ │ │ │ -9FC52 UID Size 04 (4) │ │ │ │ -9FC53 UID 00000000 (0) │ │ │ │ -9FC57 GID Size 04 (4) │ │ │ │ -9FC58 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FC5C CENTRAL HEADER #28 02014B50 (33639248) │ │ │ │ -9FC60 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FC61 Created OS 03 (3) 'Unix' │ │ │ │ -9FC62 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FC63 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FC64 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FC66 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FC68 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FC6C CRC F2ED2F43 (4075630403) │ │ │ │ -9FC70 Compressed Size 00001263 (4707) │ │ │ │ -9FC74 Uncompressed Size 0000346A (13418) │ │ │ │ -9FC78 Filename Length 0014 (20) │ │ │ │ -9FC7A Extra Length 0018 (24) │ │ │ │ -9FC7C Comment Length 0000 (0) │ │ │ │ -9FC7E Disk Start 0000 (0) │ │ │ │ -9FC80 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FC82 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FC86 Local Header Offset 00036CF3 (224499) │ │ │ │ -9FC8A Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FC8A: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FC9E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FCA0 Length 0005 (5) │ │ │ │ -9FCA2 Flags 01 (1) 'Modification' │ │ │ │ -9FCA3 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FCA7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FCA9 Length 000B (11) │ │ │ │ -9FCAB Version 01 (1) │ │ │ │ -9FCAC UID Size 04 (4) │ │ │ │ -9FCAD UID 00000000 (0) │ │ │ │ -9FCB1 GID Size 04 (4) │ │ │ │ -9FCB2 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FCB6 CENTRAL HEADER #29 02014B50 (33639248) │ │ │ │ -9FCBA Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FCBB Created OS 03 (3) 'Unix' │ │ │ │ -9FCBC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FCBD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FCBE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FCC0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FCC2 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FCC6 CRC 6E1AD5C6 (1847252422) │ │ │ │ -9FCCA Compressed Size 00000AD0 (2768) │ │ │ │ -9FCCE Uncompressed Size 00002300 (8960) │ │ │ │ -9FCD2 Filename Length 001B (27) │ │ │ │ -9FCD4 Extra Length 0018 (24) │ │ │ │ -9FCD6 Comment Length 0000 (0) │ │ │ │ -9FCD8 Disk Start 0000 (0) │ │ │ │ -9FCDA Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FCDC Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FCE0 Local Header Offset 00037FA4 (229284) │ │ │ │ -9FCE4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FCE4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FCFF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FD01 Length 0005 (5) │ │ │ │ -9FD03 Flags 01 (1) 'Modification' │ │ │ │ -9FD04 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FD08 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FD0A Length 000B (11) │ │ │ │ -9FD0C Version 01 (1) │ │ │ │ -9FD0D UID Size 04 (4) │ │ │ │ -9FD0E UID 00000000 (0) │ │ │ │ -9FD12 GID Size 04 (4) │ │ │ │ -9FD13 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FD17 CENTRAL HEADER #30 02014B50 (33639248) │ │ │ │ -9FD1B Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FD1C Created OS 03 (3) 'Unix' │ │ │ │ -9FD1D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FD1E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FD1F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FD21 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FD23 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FD27 CRC F988B6A5 (4186486437) │ │ │ │ -9FD2B Compressed Size 00000A93 (2707) │ │ │ │ -9FD2F Uncompressed Size 00002365 (9061) │ │ │ │ -9FD33 Filename Length 0013 (19) │ │ │ │ -9FD35 Extra Length 0018 (24) │ │ │ │ -9FD37 Comment Length 0000 (0) │ │ │ │ -9FD39 Disk Start 0000 (0) │ │ │ │ -9FD3B Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FD3D Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FD41 Local Header Offset 00038AC9 (232137) │ │ │ │ -9FD45 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FD45: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FD58 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FD5A Length 0005 (5) │ │ │ │ -9FD5C Flags 01 (1) 'Modification' │ │ │ │ -9FD5D Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FD61 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FD63 Length 000B (11) │ │ │ │ -9FD65 Version 01 (1) │ │ │ │ -9FD66 UID Size 04 (4) │ │ │ │ -9FD67 UID 00000000 (0) │ │ │ │ -9FD6B GID Size 04 (4) │ │ │ │ -9FD6C GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FD70 CENTRAL HEADER #31 02014B50 (33639248) │ │ │ │ -9FD74 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FD75 Created OS 03 (3) 'Unix' │ │ │ │ -9FD76 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FD77 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FD78 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FD7A Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FD7C Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FD80 CRC A8B2957D (2830275965) │ │ │ │ -9FD84 Compressed Size 000010DB (4315) │ │ │ │ -9FD88 Uncompressed Size 000055E3 (21987) │ │ │ │ -9FD8C Filename Length 000F (15) │ │ │ │ -9FD8E Extra Length 0018 (24) │ │ │ │ -9FD90 Comment Length 0000 (0) │ │ │ │ -9FD92 Disk Start 0000 (0) │ │ │ │ -9FD94 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FD96 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FD9A Local Header Offset 000395A9 (234921) │ │ │ │ -9FD9E Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FD9E: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FDAD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FDAF Length 0005 (5) │ │ │ │ -9FDB1 Flags 01 (1) 'Modification' │ │ │ │ -9FDB2 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FDB6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FDB8 Length 000B (11) │ │ │ │ -9FDBA Version 01 (1) │ │ │ │ -9FDBB UID Size 04 (4) │ │ │ │ -9FDBC UID 00000000 (0) │ │ │ │ -9FDC0 GID Size 04 (4) │ │ │ │ -9FDC1 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FDC5 CENTRAL HEADER #32 02014B50 (33639248) │ │ │ │ -9FDC9 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FDCA Created OS 03 (3) 'Unix' │ │ │ │ -9FDCB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FDCC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FDCD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FDCF Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FDD1 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FDD5 CRC C70CE575 (3339511157) │ │ │ │ -9FDD9 Compressed Size 0000066B (1643) │ │ │ │ -9FDDD Uncompressed Size 000018E0 (6368) │ │ │ │ -9FDE1 Filename Length 000F (15) │ │ │ │ -9FDE3 Extra Length 0018 (24) │ │ │ │ -9FDE5 Comment Length 0000 (0) │ │ │ │ -9FDE7 Disk Start 0000 (0) │ │ │ │ -9FDE9 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FDEB Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FDEF Local Header Offset 0003A6CD (239309) │ │ │ │ -9FDF3 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FDF3: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FE02 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FE04 Length 0005 (5) │ │ │ │ -9FE06 Flags 01 (1) 'Modification' │ │ │ │ -9FE07 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FE0B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FE0D Length 000B (11) │ │ │ │ -9FE0F Version 01 (1) │ │ │ │ -9FE10 UID Size 04 (4) │ │ │ │ -9FE11 UID 00000000 (0) │ │ │ │ -9FE15 GID Size 04 (4) │ │ │ │ -9FE16 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FE1A CENTRAL HEADER #33 02014B50 (33639248) │ │ │ │ -9FE1E Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FE1F Created OS 03 (3) 'Unix' │ │ │ │ -9FE20 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FE21 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FE22 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FE24 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FE26 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FE2A CRC 3F62BBB5 (1063435189) │ │ │ │ -9FE2E Compressed Size 00001A60 (6752) │ │ │ │ -9FE32 Uncompressed Size 000065AF (26031) │ │ │ │ -9FE36 Filename Length 0013 (19) │ │ │ │ -9FE38 Extra Length 0018 (24) │ │ │ │ -9FE3A Comment Length 0000 (0) │ │ │ │ -9FE3C Disk Start 0000 (0) │ │ │ │ -9FE3E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FE40 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FE44 Local Header Offset 0003AD81 (241025) │ │ │ │ -9FE48 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FE48: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FE5B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FE5D Length 0005 (5) │ │ │ │ -9FE5F Flags 01 (1) 'Modification' │ │ │ │ -9FE60 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FE64 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FE66 Length 000B (11) │ │ │ │ -9FE68 Version 01 (1) │ │ │ │ -9FE69 UID Size 04 (4) │ │ │ │ -9FE6A UID 00000000 (0) │ │ │ │ -9FE6E GID Size 04 (4) │ │ │ │ -9FE6F GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FE73 CENTRAL HEADER #34 02014B50 (33639248) │ │ │ │ -9FE77 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FE78 Created OS 03 (3) 'Unix' │ │ │ │ -9FE79 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FE7A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FE7B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FE7D Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FE7F Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FE83 CRC BA8C2020 (3129745440) │ │ │ │ -9FE87 Compressed Size 000009A7 (2471) │ │ │ │ -9FE8B Uncompressed Size 00001B65 (7013) │ │ │ │ -9FE8F Filename Length 0010 (16) │ │ │ │ -9FE91 Extra Length 0018 (24) │ │ │ │ -9FE93 Comment Length 0000 (0) │ │ │ │ -9FE95 Disk Start 0000 (0) │ │ │ │ -9FE97 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FE99 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FE9D Local Header Offset 0003C82E (247854) │ │ │ │ -9FEA1 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FEA1: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FEB1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FEB3 Length 0005 (5) │ │ │ │ -9FEB5 Flags 01 (1) 'Modification' │ │ │ │ -9FEB6 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FEBA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FEBC Length 000B (11) │ │ │ │ -9FEBE Version 01 (1) │ │ │ │ -9FEBF UID Size 04 (4) │ │ │ │ -9FEC0 UID 00000000 (0) │ │ │ │ -9FEC4 GID Size 04 (4) │ │ │ │ -9FEC5 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FEC9 CENTRAL HEADER #35 02014B50 (33639248) │ │ │ │ -9FECD Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FECE Created OS 03 (3) 'Unix' │ │ │ │ -9FECF Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FED0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FED1 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FED3 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FED5 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FED9 CRC 61869F50 (1636212560) │ │ │ │ -9FEDD Compressed Size 000006B9 (1721) │ │ │ │ -9FEE1 Uncompressed Size 00001566 (5478) │ │ │ │ -9FEE5 Filename Length 0012 (18) │ │ │ │ -9FEE7 Extra Length 0018 (24) │ │ │ │ -9FEE9 Comment Length 0000 (0) │ │ │ │ -9FEEB Disk Start 0000 (0) │ │ │ │ -9FEED Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FEEF Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FEF3 Local Header Offset 0003D21F (250399) │ │ │ │ -9FEF7 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FEF7: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FF09 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FF0B Length 0005 (5) │ │ │ │ -9FF0D Flags 01 (1) 'Modification' │ │ │ │ -9FF0E Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FF12 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FF14 Length 000B (11) │ │ │ │ -9FF16 Version 01 (1) │ │ │ │ -9FF17 UID Size 04 (4) │ │ │ │ -9FF18 UID 00000000 (0) │ │ │ │ -9FF1C GID Size 04 (4) │ │ │ │ -9FF1D GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FF21 CENTRAL HEADER #36 02014B50 (33639248) │ │ │ │ -9FF25 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FF26 Created OS 03 (3) 'Unix' │ │ │ │ -9FF27 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FF28 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FF29 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FF2B Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FF2D Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FF31 CRC 1EE70229 (518455849) │ │ │ │ -9FF35 Compressed Size 00002A16 (10774) │ │ │ │ -9FF39 Uncompressed Size 0000B1DD (45533) │ │ │ │ -9FF3D Filename Length 0010 (16) │ │ │ │ -9FF3F Extra Length 0018 (24) │ │ │ │ -9FF41 Comment Length 0000 (0) │ │ │ │ -9FF43 Disk Start 0000 (0) │ │ │ │ -9FF45 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FF47 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FF4B Local Header Offset 0003D924 (252196) │ │ │ │ -9FF4F Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FF4F: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FF5F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FF61 Length 0005 (5) │ │ │ │ -9FF63 Flags 01 (1) 'Modification' │ │ │ │ -9FF64 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FF68 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FF6A Length 000B (11) │ │ │ │ -9FF6C Version 01 (1) │ │ │ │ -9FF6D UID Size 04 (4) │ │ │ │ -9FF6E UID 00000000 (0) │ │ │ │ -9FF72 GID Size 04 (4) │ │ │ │ -9FF73 GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FF77 CENTRAL HEADER #37 02014B50 (33639248) │ │ │ │ -9FF7B Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FF7C Created OS 03 (3) 'Unix' │ │ │ │ -9FF7D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FF7E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FF7F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FF81 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FF83 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FF87 CRC A48C5994 (2760661396) │ │ │ │ -9FF8B Compressed Size 00001E8A (7818) │ │ │ │ -9FF8F Uncompressed Size 00009940 (39232) │ │ │ │ -9FF93 Filename Length 0012 (18) │ │ │ │ -9FF95 Extra Length 0018 (24) │ │ │ │ -9FF97 Comment Length 0000 (0) │ │ │ │ -9FF99 Disk Start 0000 (0) │ │ │ │ -9FF9B Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FF9D Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FFA1 Local Header Offset 00040384 (263044) │ │ │ │ -9FFA5 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FFA5: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -9FFB7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -9FFB9 Length 0005 (5) │ │ │ │ -9FFBB Flags 01 (1) 'Modification' │ │ │ │ -9FFBC Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FFC0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -9FFC2 Length 000B (11) │ │ │ │ -9FFC4 Version 01 (1) │ │ │ │ -9FFC5 UID Size 04 (4) │ │ │ │ -9FFC6 UID 00000000 (0) │ │ │ │ -9FFCA GID Size 04 (4) │ │ │ │ -9FFCB GID 00000000 (0) │ │ │ │ - │ │ │ │ -9FFCF CENTRAL HEADER #38 02014B50 (33639248) │ │ │ │ -9FFD3 Created Zip Spec 3D (61) '6.1' │ │ │ │ -9FFD4 Created OS 03 (3) 'Unix' │ │ │ │ -9FFD5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -9FFD6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -9FFD7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -9FFD9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -9FFDB Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -9FFDF CRC 3DD39FE9 (1037279209) │ │ │ │ -9FFE3 Compressed Size 0000144E (5198) │ │ │ │ -9FFE7 Uncompressed Size 00007A46 (31302) │ │ │ │ -9FFEB Filename Length 0018 (24) │ │ │ │ -9FFED Extra Length 0018 (24) │ │ │ │ -9FFEF Comment Length 0000 (0) │ │ │ │ -9FFF1 Disk Start 0000 (0) │ │ │ │ -9FFF3 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -9FFF5 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -9FFF9 Local Header Offset 0004225A (270938) │ │ │ │ -9FFFD Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0x9FFFD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0015 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0017 Length 0005 (5) │ │ │ │ -A0019 Flags 01 (1) 'Modification' │ │ │ │ -A001A Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A001E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0020 Length 000B (11) │ │ │ │ -A0022 Version 01 (1) │ │ │ │ -A0023 UID Size 04 (4) │ │ │ │ -A0024 UID 00000000 (0) │ │ │ │ -A0028 GID Size 04 (4) │ │ │ │ -A0029 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A002D CENTRAL HEADER #39 02014B50 (33639248) │ │ │ │ -A0031 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0032 Created OS 03 (3) 'Unix' │ │ │ │ -A0033 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0034 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0035 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0037 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0039 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A003D CRC FD9E19CB (4254996939) │ │ │ │ -A0041 Compressed Size 000018D4 (6356) │ │ │ │ -A0045 Uncompressed Size 0000A83A (43066) │ │ │ │ -A0049 Filename Length 001F (31) │ │ │ │ -A004B Extra Length 0018 (24) │ │ │ │ -A004D Comment Length 0000 (0) │ │ │ │ -A004F Disk Start 0000 (0) │ │ │ │ -A0051 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0053 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0057 Local Header Offset 000436FA (276218) │ │ │ │ -A005B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA005B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A007A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A007C Length 0005 (5) │ │ │ │ -A007E Flags 01 (1) 'Modification' │ │ │ │ -A007F Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0083 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0085 Length 000B (11) │ │ │ │ -A0087 Version 01 (1) │ │ │ │ -A0088 UID Size 04 (4) │ │ │ │ -A0089 UID 00000000 (0) │ │ │ │ -A008D GID Size 04 (4) │ │ │ │ -A008E GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0092 CENTRAL HEADER #40 02014B50 (33639248) │ │ │ │ -A0096 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0097 Created OS 03 (3) 'Unix' │ │ │ │ -A0098 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0099 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A009A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A009C Compression Method 0008 (8) 'Deflated' │ │ │ │ -A009E Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A00A2 CRC 8E4EBF9E (2387525534) │ │ │ │ -A00A6 Compressed Size 000003F8 (1016) │ │ │ │ -A00AA Uncompressed Size 000008A4 (2212) │ │ │ │ -A00AE Filename Length 001E (30) │ │ │ │ -A00B0 Extra Length 0018 (24) │ │ │ │ -A00B2 Comment Length 0000 (0) │ │ │ │ -A00B4 Disk Start 0000 (0) │ │ │ │ -A00B6 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A00B8 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A00BC Local Header Offset 00045027 (282663) │ │ │ │ -A00C0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA00C0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A00DE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A00E0 Length 0005 (5) │ │ │ │ -A00E2 Flags 01 (1) 'Modification' │ │ │ │ -A00E3 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A00E7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A00E9 Length 000B (11) │ │ │ │ -A00EB Version 01 (1) │ │ │ │ -A00EC UID Size 04 (4) │ │ │ │ -A00ED UID 00000000 (0) │ │ │ │ -A00F1 GID Size 04 (4) │ │ │ │ -A00F2 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A00F6 CENTRAL HEADER #41 02014B50 (33639248) │ │ │ │ -A00FA Created Zip Spec 3D (61) '6.1' │ │ │ │ -A00FB Created OS 03 (3) 'Unix' │ │ │ │ -A00FC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A00FD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A00FE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0100 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0102 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0106 CRC D9FD2FF3 (3657248755) │ │ │ │ -A010A Compressed Size 00004296 (17046) │ │ │ │ -A010E Uncompressed Size 0000D8E8 (55528) │ │ │ │ -A0112 Filename Length 0013 (19) │ │ │ │ -A0114 Extra Length 0018 (24) │ │ │ │ -A0116 Comment Length 0000 (0) │ │ │ │ -A0118 Disk Start 0000 (0) │ │ │ │ -A011A Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A011C Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0120 Local Header Offset 00045477 (283767) │ │ │ │ -A0124 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0124: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0137 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0139 Length 0005 (5) │ │ │ │ -A013B Flags 01 (1) 'Modification' │ │ │ │ -A013C Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0140 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0142 Length 000B (11) │ │ │ │ -A0144 Version 01 (1) │ │ │ │ -A0145 UID Size 04 (4) │ │ │ │ -A0146 UID 00000000 (0) │ │ │ │ -A014A GID Size 04 (4) │ │ │ │ -A014B GID 00000000 (0) │ │ │ │ - │ │ │ │ -A014F CENTRAL HEADER #42 02014B50 (33639248) │ │ │ │ -A0153 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0154 Created OS 03 (3) 'Unix' │ │ │ │ -A0155 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0156 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0157 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0159 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A015B Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A015F CRC 9236E128 (2453070120) │ │ │ │ -A0163 Compressed Size 000026C5 (9925) │ │ │ │ -A0167 Uncompressed Size 00006E46 (28230) │ │ │ │ -A016B Filename Length 0019 (25) │ │ │ │ -A016D Extra Length 0018 (24) │ │ │ │ -A016F Comment Length 0000 (0) │ │ │ │ -A0171 Disk Start 0000 (0) │ │ │ │ -A0173 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0175 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0179 Local Header Offset 0004975A (300890) │ │ │ │ -A017D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA017D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0196 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0198 Length 0005 (5) │ │ │ │ -A019A Flags 01 (1) 'Modification' │ │ │ │ -A019B Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A019F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A01A1 Length 000B (11) │ │ │ │ -A01A3 Version 01 (1) │ │ │ │ -A01A4 UID Size 04 (4) │ │ │ │ -A01A5 UID 00000000 (0) │ │ │ │ -A01A9 GID Size 04 (4) │ │ │ │ -A01AA GID 00000000 (0) │ │ │ │ - │ │ │ │ -A01AE CENTRAL HEADER #43 02014B50 (33639248) │ │ │ │ -A01B2 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A01B3 Created OS 03 (3) 'Unix' │ │ │ │ -A01B4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A01B5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A01B6 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A01B8 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A01BA Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A01BE CRC A84F7DF4 (2823781876) │ │ │ │ -A01C2 Compressed Size 0000273B (10043) │ │ │ │ -A01C6 Uncompressed Size 00008B84 (35716) │ │ │ │ -A01CA Filename Length 0019 (25) │ │ │ │ -A01CC Extra Length 0018 (24) │ │ │ │ -A01CE Comment Length 0000 (0) │ │ │ │ -A01D0 Disk Start 0000 (0) │ │ │ │ -A01D2 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A01D4 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A01D8 Local Header Offset 0004BE72 (310898) │ │ │ │ -A01DC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA01DC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A01F5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A01F7 Length 0005 (5) │ │ │ │ -A01F9 Flags 01 (1) 'Modification' │ │ │ │ -A01FA Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A01FE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0200 Length 000B (11) │ │ │ │ -A0202 Version 01 (1) │ │ │ │ -A0203 UID Size 04 (4) │ │ │ │ -A0204 UID 00000000 (0) │ │ │ │ -A0208 GID Size 04 (4) │ │ │ │ -A0209 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A020D CENTRAL HEADER #44 02014B50 (33639248) │ │ │ │ -A0211 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0212 Created OS 03 (3) 'Unix' │ │ │ │ -A0213 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0214 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0215 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0217 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0219 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A021D CRC 4C908684 (1284540036) │ │ │ │ -A0221 Compressed Size 00000CFC (3324) │ │ │ │ -A0225 Uncompressed Size 0000517B (20859) │ │ │ │ -A0229 Filename Length 0021 (33) │ │ │ │ -A022B Extra Length 0018 (24) │ │ │ │ -A022D Comment Length 0000 (0) │ │ │ │ -A022F Disk Start 0000 (0) │ │ │ │ -A0231 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0233 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0237 Local Header Offset 0004E600 (321024) │ │ │ │ -A023B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA023B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A025C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A025E Length 0005 (5) │ │ │ │ -A0260 Flags 01 (1) 'Modification' │ │ │ │ -A0261 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0265 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0267 Length 000B (11) │ │ │ │ -A0269 Version 01 (1) │ │ │ │ -A026A UID Size 04 (4) │ │ │ │ -A026B UID 00000000 (0) │ │ │ │ -A026F GID Size 04 (4) │ │ │ │ -A0270 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0274 CENTRAL HEADER #45 02014B50 (33639248) │ │ │ │ -A0278 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0279 Created OS 03 (3) 'Unix' │ │ │ │ -A027A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A027B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A027C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A027E Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0280 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0284 CRC 4527F9F0 (1160247792) │ │ │ │ -A0288 Compressed Size 00000469 (1129) │ │ │ │ -A028C Uncompressed Size 00000932 (2354) │ │ │ │ -A0290 Filename Length 001B (27) │ │ │ │ -A0292 Extra Length 0018 (24) │ │ │ │ -A0294 Comment Length 0000 (0) │ │ │ │ -A0296 Disk Start 0000 (0) │ │ │ │ -A0298 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A029A Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A029E Local Header Offset 0004F357 (324439) │ │ │ │ -A02A2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA02A2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A02BD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A02BF Length 0005 (5) │ │ │ │ -A02C1 Flags 01 (1) 'Modification' │ │ │ │ -A02C2 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A02C6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A02C8 Length 000B (11) │ │ │ │ -A02CA Version 01 (1) │ │ │ │ -A02CB UID Size 04 (4) │ │ │ │ -A02CC UID 00000000 (0) │ │ │ │ -A02D0 GID Size 04 (4) │ │ │ │ -A02D1 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A02D5 CENTRAL HEADER #46 02014B50 (33639248) │ │ │ │ -A02D9 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A02DA Created OS 03 (3) 'Unix' │ │ │ │ -A02DB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A02DC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A02DD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A02DF Compression Method 0008 (8) 'Deflated' │ │ │ │ -A02E1 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A02E5 CRC 18CB2039 (415965241) │ │ │ │ -A02E9 Compressed Size 000016FF (5887) │ │ │ │ -A02ED Uncompressed Size 00007A12 (31250) │ │ │ │ -A02F1 Filename Length 001F (31) │ │ │ │ -A02F3 Extra Length 0018 (24) │ │ │ │ -A02F5 Comment Length 0000 (0) │ │ │ │ -A02F7 Disk Start 0000 (0) │ │ │ │ -A02F9 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A02FB Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A02FF Local Header Offset 0004F815 (325653) │ │ │ │ -A0303 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0303: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0322 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0324 Length 0005 (5) │ │ │ │ -A0326 Flags 01 (1) 'Modification' │ │ │ │ -A0327 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A032B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A032D Length 000B (11) │ │ │ │ -A032F Version 01 (1) │ │ │ │ -A0330 UID Size 04 (4) │ │ │ │ -A0331 UID 00000000 (0) │ │ │ │ -A0335 GID Size 04 (4) │ │ │ │ -A0336 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A033A CENTRAL HEADER #47 02014B50 (33639248) │ │ │ │ -A033E Created Zip Spec 3D (61) '6.1' │ │ │ │ -A033F Created OS 03 (3) 'Unix' │ │ │ │ -A0340 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0341 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0342 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0344 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0346 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A034A CRC 6269335F (1651061599) │ │ │ │ -A034E Compressed Size 00004160 (16736) │ │ │ │ -A0352 Uncompressed Size 0001D147 (119111) │ │ │ │ -A0356 Filename Length 0010 (16) │ │ │ │ -A0358 Extra Length 0018 (24) │ │ │ │ -A035A Comment Length 0000 (0) │ │ │ │ -A035C Disk Start 0000 (0) │ │ │ │ -A035E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0360 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0364 Local Header Offset 00050F6D (331629) │ │ │ │ -A0368 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0368: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0378 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A037A Length 0005 (5) │ │ │ │ -A037C Flags 01 (1) 'Modification' │ │ │ │ -A037D Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0381 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0383 Length 000B (11) │ │ │ │ -A0385 Version 01 (1) │ │ │ │ -A0386 UID Size 04 (4) │ │ │ │ -A0387 UID 00000000 (0) │ │ │ │ -A038B GID Size 04 (4) │ │ │ │ -A038C GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0390 CENTRAL HEADER #48 02014B50 (33639248) │ │ │ │ -A0394 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0395 Created OS 03 (3) 'Unix' │ │ │ │ -A0396 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0397 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0398 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A039A Compression Method 0008 (8) 'Deflated' │ │ │ │ -A039C Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A03A0 CRC 1C486A07 (474507783) │ │ │ │ -A03A4 Compressed Size 00000A98 (2712) │ │ │ │ -A03A8 Uncompressed Size 00002106 (8454) │ │ │ │ -A03AC Filename Length 0014 (20) │ │ │ │ -A03AE Extra Length 0018 (24) │ │ │ │ -A03B0 Comment Length 0000 (0) │ │ │ │ -A03B2 Disk Start 0000 (0) │ │ │ │ -A03B4 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A03B6 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A03BA Local Header Offset 00055117 (348439) │ │ │ │ -A03BE Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA03BE: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A03D2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A03D4 Length 0005 (5) │ │ │ │ -A03D6 Flags 01 (1) 'Modification' │ │ │ │ -A03D7 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A03DB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A03DD Length 000B (11) │ │ │ │ -A03DF Version 01 (1) │ │ │ │ -A03E0 UID Size 04 (4) │ │ │ │ -A03E1 UID 00000000 (0) │ │ │ │ -A03E5 GID Size 04 (4) │ │ │ │ -A03E6 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A03EA CENTRAL HEADER #49 02014B50 (33639248) │ │ │ │ -A03EE Created Zip Spec 3D (61) '6.1' │ │ │ │ -A03EF Created OS 03 (3) 'Unix' │ │ │ │ -A03F0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A03F1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A03F2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A03F4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A03F6 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A03FA CRC 94315B0D (2486262541) │ │ │ │ -A03FE Compressed Size 0000AC13 (44051) │ │ │ │ -A0402 Uncompressed Size 0003E93F (256319) │ │ │ │ -A0406 Filename Length 0017 (23) │ │ │ │ -A0408 Extra Length 0018 (24) │ │ │ │ -A040A Comment Length 0000 (0) │ │ │ │ -A040C Disk Start 0000 (0) │ │ │ │ -A040E Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0410 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0414 Local Header Offset 00055BFD (351229) │ │ │ │ -A0418 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0418: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A042F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0431 Length 0005 (5) │ │ │ │ -A0433 Flags 01 (1) 'Modification' │ │ │ │ -A0434 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0438 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A043A Length 000B (11) │ │ │ │ -A043C Version 01 (1) │ │ │ │ -A043D UID Size 04 (4) │ │ │ │ -A043E UID 00000000 (0) │ │ │ │ -A0442 GID Size 04 (4) │ │ │ │ -A0443 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0447 CENTRAL HEADER #50 02014B50 (33639248) │ │ │ │ -A044B Created Zip Spec 3D (61) '6.1' │ │ │ │ -A044C Created OS 03 (3) 'Unix' │ │ │ │ -A044D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A044E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A044F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0451 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0453 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0457 CRC EA7F3AE6 (3934206694) │ │ │ │ -A045B Compressed Size 00000463 (1123) │ │ │ │ -A045F Uncompressed Size 00000DF4 (3572) │ │ │ │ -A0463 Filename Length 0013 (19) │ │ │ │ -A0465 Extra Length 0018 (24) │ │ │ │ -A0467 Comment Length 0000 (0) │ │ │ │ -A0469 Disk Start 0000 (0) │ │ │ │ -A046B Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A046D Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0471 Local Header Offset 00060861 (395361) │ │ │ │ -A0475 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0475: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0488 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A048A Length 0005 (5) │ │ │ │ -A048C Flags 01 (1) 'Modification' │ │ │ │ -A048D Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0491 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0493 Length 000B (11) │ │ │ │ -A0495 Version 01 (1) │ │ │ │ -A0496 UID Size 04 (4) │ │ │ │ -A0497 UID 00000000 (0) │ │ │ │ -A049B GID Size 04 (4) │ │ │ │ -A049C GID 00000000 (0) │ │ │ │ - │ │ │ │ -A04A0 CENTRAL HEADER #51 02014B50 (33639248) │ │ │ │ -A04A4 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A04A5 Created OS 03 (3) 'Unix' │ │ │ │ -A04A6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A04A7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A04A8 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A04AA Compression Method 0008 (8) 'Deflated' │ │ │ │ -A04AC Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A04B0 CRC 8E059064 (2382729316) │ │ │ │ -A04B4 Compressed Size 000014B4 (5300) │ │ │ │ -A04B8 Uncompressed Size 000067DB (26587) │ │ │ │ -A04BC Filename Length 0012 (18) │ │ │ │ -A04BE Extra Length 0018 (24) │ │ │ │ -A04C0 Comment Length 0000 (0) │ │ │ │ -A04C2 Disk Start 0000 (0) │ │ │ │ -A04C4 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A04C6 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A04CA Local Header Offset 00060D11 (396561) │ │ │ │ -A04CE Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA04CE: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A04E0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A04E2 Length 0005 (5) │ │ │ │ -A04E4 Flags 01 (1) 'Modification' │ │ │ │ -A04E5 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A04E9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A04EB Length 000B (11) │ │ │ │ -A04ED Version 01 (1) │ │ │ │ -A04EE UID Size 04 (4) │ │ │ │ -A04EF UID 00000000 (0) │ │ │ │ -A04F3 GID Size 04 (4) │ │ │ │ -A04F4 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A04F8 CENTRAL HEADER #52 02014B50 (33639248) │ │ │ │ -A04FC Created Zip Spec 3D (61) '6.1' │ │ │ │ -A04FD Created OS 03 (3) 'Unix' │ │ │ │ -A04FE Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A04FF Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0500 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0502 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0504 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0508 CRC 5454A1EE (1414832622) │ │ │ │ -A050C Compressed Size 000011F0 (4592) │ │ │ │ -A0510 Uncompressed Size 0000410D (16653) │ │ │ │ -A0514 Filename Length 0012 (18) │ │ │ │ -A0516 Extra Length 0018 (24) │ │ │ │ -A0518 Comment Length 0000 (0) │ │ │ │ -A051A Disk Start 0000 (0) │ │ │ │ -A051C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A051E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0522 Local Header Offset 00062211 (401937) │ │ │ │ -A0526 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0526: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0538 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A053A Length 0005 (5) │ │ │ │ -A053C Flags 01 (1) 'Modification' │ │ │ │ -A053D Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0541 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0543 Length 000B (11) │ │ │ │ -A0545 Version 01 (1) │ │ │ │ -A0546 UID Size 04 (4) │ │ │ │ -A0547 UID 00000000 (0) │ │ │ │ -A054B GID Size 04 (4) │ │ │ │ -A054C GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0550 CENTRAL HEADER #53 02014B50 (33639248) │ │ │ │ -A0554 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0555 Created OS 03 (3) 'Unix' │ │ │ │ -A0556 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0557 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0558 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A055A Compression Method 0008 (8) 'Deflated' │ │ │ │ -A055C Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0560 CRC C8A917EC (3366524908) │ │ │ │ -A0564 Compressed Size 000009DB (2523) │ │ │ │ -A0568 Uncompressed Size 0000352A (13610) │ │ │ │ -A056C Filename Length 0019 (25) │ │ │ │ -A056E Extra Length 0018 (24) │ │ │ │ -A0570 Comment Length 0000 (0) │ │ │ │ -A0572 Disk Start 0000 (0) │ │ │ │ -A0574 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0576 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A057A Local Header Offset 0006344D (406605) │ │ │ │ -A057E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA057E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0597 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0599 Length 0005 (5) │ │ │ │ -A059B Flags 01 (1) 'Modification' │ │ │ │ -A059C Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A05A0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A05A2 Length 000B (11) │ │ │ │ -A05A4 Version 01 (1) │ │ │ │ -A05A5 UID Size 04 (4) │ │ │ │ -A05A6 UID 00000000 (0) │ │ │ │ -A05AA GID Size 04 (4) │ │ │ │ -A05AB GID 00000000 (0) │ │ │ │ - │ │ │ │ -A05AF CENTRAL HEADER #54 02014B50 (33639248) │ │ │ │ -A05B3 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A05B4 Created OS 03 (3) 'Unix' │ │ │ │ -A05B5 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A05B6 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A05B7 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A05B9 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A05BB Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A05BF CRC 047C3EB7 (75251383) │ │ │ │ -A05C3 Compressed Size 00002039 (8249) │ │ │ │ -A05C7 Uncompressed Size 00010919 (67865) │ │ │ │ -A05CB Filename Length 0019 (25) │ │ │ │ -A05CD Extra Length 0018 (24) │ │ │ │ -A05CF Comment Length 0000 (0) │ │ │ │ -A05D1 Disk Start 0000 (0) │ │ │ │ -A05D3 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A05D5 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A05D9 Local Header Offset 00063E7B (409211) │ │ │ │ -A05DD Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA05DD: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A05F6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A05F8 Length 0005 (5) │ │ │ │ -A05FA Flags 01 (1) 'Modification' │ │ │ │ -A05FB Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A05FF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0601 Length 000B (11) │ │ │ │ -A0603 Version 01 (1) │ │ │ │ -A0604 UID Size 04 (4) │ │ │ │ -A0605 UID 00000000 (0) │ │ │ │ -A0609 GID Size 04 (4) │ │ │ │ -A060A GID 00000000 (0) │ │ │ │ - │ │ │ │ -A060E CENTRAL HEADER #55 02014B50 (33639248) │ │ │ │ -A0612 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0613 Created OS 03 (3) 'Unix' │ │ │ │ -A0614 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0615 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0616 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0618 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A061A Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A061E CRC D0372DB1 (3493277105) │ │ │ │ -A0622 Compressed Size 0000177F (6015) │ │ │ │ -A0626 Uncompressed Size 0000472D (18221) │ │ │ │ -A062A Filename Length 0014 (20) │ │ │ │ -A062C Extra Length 0018 (24) │ │ │ │ -A062E Comment Length 0000 (0) │ │ │ │ -A0630 Disk Start 0000 (0) │ │ │ │ -A0632 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0634 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0638 Local Header Offset 00065F07 (417543) │ │ │ │ -A063C Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA063C: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0650 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0652 Length 0005 (5) │ │ │ │ -A0654 Flags 01 (1) 'Modification' │ │ │ │ -A0655 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0659 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A065B Length 000B (11) │ │ │ │ -A065D Version 01 (1) │ │ │ │ -A065E UID Size 04 (4) │ │ │ │ -A065F UID 00000000 (0) │ │ │ │ -A0663 GID Size 04 (4) │ │ │ │ -A0664 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0668 CENTRAL HEADER #56 02014B50 (33639248) │ │ │ │ -A066C Created Zip Spec 3D (61) '6.1' │ │ │ │ -A066D Created OS 03 (3) 'Unix' │ │ │ │ -A066E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A066F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0670 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0672 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0674 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0678 CRC FAE30038 (4209180728) │ │ │ │ -A067C Compressed Size 0000040B (1035) │ │ │ │ -A0680 Uncompressed Size 00000826 (2086) │ │ │ │ -A0684 Filename Length 001C (28) │ │ │ │ -A0686 Extra Length 0018 (24) │ │ │ │ -A0688 Comment Length 0000 (0) │ │ │ │ -A068A Disk Start 0000 (0) │ │ │ │ -A068C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A068E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0692 Local Header Offset 000676D4 (423636) │ │ │ │ -A0696 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0696: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A06B2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A06B4 Length 0005 (5) │ │ │ │ -A06B6 Flags 01 (1) 'Modification' │ │ │ │ -A06B7 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A06BB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A06BD Length 000B (11) │ │ │ │ -A06BF Version 01 (1) │ │ │ │ -A06C0 UID Size 04 (4) │ │ │ │ -A06C1 UID 00000000 (0) │ │ │ │ -A06C5 GID Size 04 (4) │ │ │ │ -A06C6 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A06CA CENTRAL HEADER #57 02014B50 (33639248) │ │ │ │ -A06CE Created Zip Spec 3D (61) '6.1' │ │ │ │ -A06CF Created OS 03 (3) 'Unix' │ │ │ │ -A06D0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A06D1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A06D2 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A06D4 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A06D6 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A06DA CRC 60A198F7 (1621203191) │ │ │ │ -A06DE Compressed Size 000024AE (9390) │ │ │ │ -A06E2 Uncompressed Size 0000B5F9 (46585) │ │ │ │ -A06E6 Filename Length 001F (31) │ │ │ │ -A06E8 Extra Length 0018 (24) │ │ │ │ -A06EA Comment Length 0000 (0) │ │ │ │ -A06EC Disk Start 0000 (0) │ │ │ │ -A06EE Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A06F0 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A06F4 Local Header Offset 00067B35 (424757) │ │ │ │ -A06F8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA06F8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0717 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0719 Length 0005 (5) │ │ │ │ -A071B Flags 01 (1) 'Modification' │ │ │ │ -A071C Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0720 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0722 Length 000B (11) │ │ │ │ -A0724 Version 01 (1) │ │ │ │ -A0725 UID Size 04 (4) │ │ │ │ -A0726 UID 00000000 (0) │ │ │ │ -A072A GID Size 04 (4) │ │ │ │ -A072B GID 00000000 (0) │ │ │ │ - │ │ │ │ -A072F CENTRAL HEADER #58 02014B50 (33639248) │ │ │ │ -A0733 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0734 Created OS 03 (3) 'Unix' │ │ │ │ -A0735 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0736 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0737 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0739 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A073B Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A073F CRC 1B69433B (459883323) │ │ │ │ -A0743 Compressed Size 00000E5E (3678) │ │ │ │ -A0747 Uncompressed Size 0000527E (21118) │ │ │ │ -A074B Filename Length 001F (31) │ │ │ │ -A074D Extra Length 0018 (24) │ │ │ │ -A074F Comment Length 0000 (0) │ │ │ │ -A0751 Disk Start 0000 (0) │ │ │ │ -A0753 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0755 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0759 Local Header Offset 0006A03C (434236) │ │ │ │ -A075D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA075D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A077C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A077E Length 0005 (5) │ │ │ │ -A0780 Flags 01 (1) 'Modification' │ │ │ │ -A0781 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0785 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0787 Length 000B (11) │ │ │ │ -A0789 Version 01 (1) │ │ │ │ -A078A UID Size 04 (4) │ │ │ │ -A078B UID 00000000 (0) │ │ │ │ -A078F GID Size 04 (4) │ │ │ │ -A0790 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0794 CENTRAL HEADER #59 02014B50 (33639248) │ │ │ │ -A0798 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0799 Created OS 03 (3) 'Unix' │ │ │ │ -A079A Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A079B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A079C General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A079E Compression Method 0008 (8) 'Deflated' │ │ │ │ -A07A0 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A07A4 CRC D6CA7B61 (3603594081) │ │ │ │ -A07A8 Compressed Size 00000A44 (2628) │ │ │ │ -A07AC Uncompressed Size 0000244F (9295) │ │ │ │ -A07B0 Filename Length 0013 (19) │ │ │ │ -A07B2 Extra Length 0018 (24) │ │ │ │ -A07B4 Comment Length 0000 (0) │ │ │ │ -A07B6 Disk Start 0000 (0) │ │ │ │ -A07B8 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A07BA Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A07BE Local Header Offset 0006AEF3 (438003) │ │ │ │ -A07C2 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA07C2: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A07D5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A07D7 Length 0005 (5) │ │ │ │ -A07D9 Flags 01 (1) 'Modification' │ │ │ │ -A07DA Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A07DE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A07E0 Length 000B (11) │ │ │ │ -A07E2 Version 01 (1) │ │ │ │ -A07E3 UID Size 04 (4) │ │ │ │ -A07E4 UID 00000000 (0) │ │ │ │ -A07E8 GID Size 04 (4) │ │ │ │ -A07E9 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A07ED CENTRAL HEADER #60 02014B50 (33639248) │ │ │ │ -A07F1 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A07F2 Created OS 03 (3) 'Unix' │ │ │ │ -A07F3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A07F4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A07F5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A07F7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A07F9 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A07FD CRC 89786723 (2306369315) │ │ │ │ -A0801 Compressed Size 0000248C (9356) │ │ │ │ -A0805 Uncompressed Size 0000B84D (47181) │ │ │ │ -A0809 Filename Length 0019 (25) │ │ │ │ -A080B Extra Length 0018 (24) │ │ │ │ -A080D Comment Length 0000 (0) │ │ │ │ -A080F Disk Start 0000 (0) │ │ │ │ -A0811 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0813 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0817 Local Header Offset 0006B984 (440708) │ │ │ │ -A081B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA081B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0834 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0836 Length 0005 (5) │ │ │ │ -A0838 Flags 01 (1) 'Modification' │ │ │ │ -A0839 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A083D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A083F Length 000B (11) │ │ │ │ -A0841 Version 01 (1) │ │ │ │ -A0842 UID Size 04 (4) │ │ │ │ -A0843 UID 00000000 (0) │ │ │ │ -A0847 GID Size 04 (4) │ │ │ │ -A0848 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A084C CENTRAL HEADER #61 02014B50 (33639248) │ │ │ │ -A0850 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0851 Created OS 03 (3) 'Unix' │ │ │ │ -A0852 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0853 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0854 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0856 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0858 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A085C CRC 0F5745D7 (257377751) │ │ │ │ -A0860 Compressed Size 00000EFA (3834) │ │ │ │ -A0864 Uncompressed Size 00003A2D (14893) │ │ │ │ -A0868 Filename Length 0024 (36) │ │ │ │ -A086A Extra Length 0018 (24) │ │ │ │ -A086C Comment Length 0000 (0) │ │ │ │ -A086E Disk Start 0000 (0) │ │ │ │ -A0870 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0872 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0876 Local Header Offset 0006DE63 (450147) │ │ │ │ -A087A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA087A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A089E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A08A0 Length 0005 (5) │ │ │ │ -A08A2 Flags 01 (1) 'Modification' │ │ │ │ -A08A3 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A08A7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A08A9 Length 000B (11) │ │ │ │ -A08AB Version 01 (1) │ │ │ │ -A08AC UID Size 04 (4) │ │ │ │ -A08AD UID 00000000 (0) │ │ │ │ -A08B1 GID Size 04 (4) │ │ │ │ -A08B2 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A08B6 CENTRAL HEADER #62 02014B50 (33639248) │ │ │ │ -A08BA Created Zip Spec 3D (61) '6.1' │ │ │ │ -A08BB Created OS 03 (3) 'Unix' │ │ │ │ -A08BC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A08BD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A08BE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A08C0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A08C2 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A08C6 CRC E4C6B7A9 (3838228393) │ │ │ │ -A08CA Compressed Size 00001ABD (6845) │ │ │ │ -A08CE Uncompressed Size 00005F39 (24377) │ │ │ │ -A08D2 Filename Length 0017 (23) │ │ │ │ -A08D4 Extra Length 0018 (24) │ │ │ │ -A08D6 Comment Length 0000 (0) │ │ │ │ -A08D8 Disk Start 0000 (0) │ │ │ │ -A08DA Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A08DC Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A08E0 Local Header Offset 0006EDBB (454075) │ │ │ │ -A08E4 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA08E4: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A08FB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A08FD Length 0005 (5) │ │ │ │ -A08FF Flags 01 (1) 'Modification' │ │ │ │ -A0900 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0904 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0906 Length 000B (11) │ │ │ │ -A0908 Version 01 (1) │ │ │ │ -A0909 UID Size 04 (4) │ │ │ │ -A090A UID 00000000 (0) │ │ │ │ -A090E GID Size 04 (4) │ │ │ │ -A090F GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0913 CENTRAL HEADER #63 02014B50 (33639248) │ │ │ │ -A0917 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0918 Created OS 03 (3) 'Unix' │ │ │ │ -A0919 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A091A Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A091B General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A091D Compression Method 0008 (8) 'Deflated' │ │ │ │ -A091F Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0923 CRC 3DD8544C (1037587532) │ │ │ │ -A0927 Compressed Size 00000ED1 (3793) │ │ │ │ -A092B Uncompressed Size 000038DE (14558) │ │ │ │ -A092F Filename Length 0023 (35) │ │ │ │ -A0931 Extra Length 0018 (24) │ │ │ │ -A0933 Comment Length 0000 (0) │ │ │ │ -A0935 Disk Start 0000 (0) │ │ │ │ -A0937 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0939 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A093D Local Header Offset 000708C9 (461001) │ │ │ │ -A0941 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0941: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0964 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0966 Length 0005 (5) │ │ │ │ -A0968 Flags 01 (1) 'Modification' │ │ │ │ -A0969 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A096D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A096F Length 000B (11) │ │ │ │ -A0971 Version 01 (1) │ │ │ │ -A0972 UID Size 04 (4) │ │ │ │ -A0973 UID 00000000 (0) │ │ │ │ -A0977 GID Size 04 (4) │ │ │ │ -A0978 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A097C CENTRAL HEADER #64 02014B50 (33639248) │ │ │ │ -A0980 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0981 Created OS 03 (3) 'Unix' │ │ │ │ -A0982 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0983 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0984 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0986 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0988 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A098C CRC 2DB7929F (767005343) │ │ │ │ -A0990 Compressed Size 00000113 (275) │ │ │ │ -A0994 Uncompressed Size 000001F3 (499) │ │ │ │ -A0998 Filename Length 001B (27) │ │ │ │ -A099A Extra Length 0018 (24) │ │ │ │ -A099C Comment Length 0000 (0) │ │ │ │ -A099E Disk Start 0000 (0) │ │ │ │ -A09A0 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A09A2 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A09A6 Local Header Offset 000717F7 (464887) │ │ │ │ -A09AA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA09AA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A09C5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A09C7 Length 0005 (5) │ │ │ │ -A09C9 Flags 01 (1) 'Modification' │ │ │ │ -A09CA Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A09CE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A09D0 Length 000B (11) │ │ │ │ -A09D2 Version 01 (1) │ │ │ │ -A09D3 UID Size 04 (4) │ │ │ │ -A09D4 UID 00000000 (0) │ │ │ │ -A09D8 GID Size 04 (4) │ │ │ │ -A09D9 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A09DD CENTRAL HEADER #65 02014B50 (33639248) │ │ │ │ -A09E1 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A09E2 Created OS 03 (3) 'Unix' │ │ │ │ -A09E3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A09E4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A09E5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A09E7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A09E9 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A09ED CRC 087EAA20 (142518816) │ │ │ │ -A09F1 Compressed Size 0000189E (6302) │ │ │ │ -A09F5 Uncompressed Size 00008F47 (36679) │ │ │ │ -A09F9 Filename Length 001D (29) │ │ │ │ -A09FB Extra Length 0018 (24) │ │ │ │ -A09FD Comment Length 0000 (0) │ │ │ │ -A09FF Disk Start 0000 (0) │ │ │ │ -A0A01 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0A03 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0A07 Local Header Offset 0007195F (465247) │ │ │ │ -A0A0B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0A0B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0A28 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0A2A Length 0005 (5) │ │ │ │ -A0A2C Flags 01 (1) 'Modification' │ │ │ │ -A0A2D Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0A31 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0A33 Length 000B (11) │ │ │ │ -A0A35 Version 01 (1) │ │ │ │ -A0A36 UID Size 04 (4) │ │ │ │ -A0A37 UID 00000000 (0) │ │ │ │ -A0A3B GID Size 04 (4) │ │ │ │ -A0A3C GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0A40 CENTRAL HEADER #66 02014B50 (33639248) │ │ │ │ -A0A44 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0A45 Created OS 03 (3) 'Unix' │ │ │ │ -A0A46 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0A47 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0A48 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0A4A Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0A4C Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0A50 CRC 516C0F5D (1366036317) │ │ │ │ -A0A54 Compressed Size 0000164A (5706) │ │ │ │ -A0A58 Uncompressed Size 00003A9C (15004) │ │ │ │ -A0A5C Filename Length 0015 (21) │ │ │ │ -A0A5E Extra Length 0018 (24) │ │ │ │ -A0A60 Comment Length 0000 (0) │ │ │ │ -A0A62 Disk Start 0000 (0) │ │ │ │ -A0A64 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0A66 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0A6A Local Header Offset 00073254 (471636) │ │ │ │ -A0A6E Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0A6E: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0A83 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0A85 Length 0005 (5) │ │ │ │ -A0A87 Flags 01 (1) 'Modification' │ │ │ │ -A0A88 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0A8C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0A8E Length 000B (11) │ │ │ │ -A0A90 Version 01 (1) │ │ │ │ -A0A91 UID Size 04 (4) │ │ │ │ -A0A92 UID 00000000 (0) │ │ │ │ -A0A96 GID Size 04 (4) │ │ │ │ -A0A97 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0A9B CENTRAL HEADER #67 02014B50 (33639248) │ │ │ │ -A0A9F Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0AA0 Created OS 03 (3) 'Unix' │ │ │ │ -A0AA1 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0AA2 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0AA3 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0AA5 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0AA7 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0AAB CRC 795D0182 (2036138370) │ │ │ │ -A0AAF Compressed Size 00003B42 (15170) │ │ │ │ -A0AB3 Uncompressed Size 00011C7F (72831) │ │ │ │ -A0AB7 Filename Length 0016 (22) │ │ │ │ -A0AB9 Extra Length 0018 (24) │ │ │ │ -A0ABB Comment Length 0000 (0) │ │ │ │ -A0ABD Disk Start 0000 (0) │ │ │ │ -A0ABF Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0AC1 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0AC5 Local Header Offset 000748ED (477421) │ │ │ │ -A0AC9 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0AC9: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0ADF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0AE1 Length 0005 (5) │ │ │ │ -A0AE3 Flags 01 (1) 'Modification' │ │ │ │ -A0AE4 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0AE8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0AEA Length 000B (11) │ │ │ │ -A0AEC Version 01 (1) │ │ │ │ -A0AED UID Size 04 (4) │ │ │ │ -A0AEE UID 00000000 (0) │ │ │ │ -A0AF2 GID Size 04 (4) │ │ │ │ -A0AF3 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0AF7 CENTRAL HEADER #68 02014B50 (33639248) │ │ │ │ -A0AFB Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0AFC Created OS 03 (3) 'Unix' │ │ │ │ -A0AFD Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0AFE Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0AFF General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0B01 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0B03 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0B07 CRC EE0EAC4F (3993939023) │ │ │ │ -A0B0B Compressed Size 00003EF5 (16117) │ │ │ │ -A0B0F Uncompressed Size 0001CBD3 (117715) │ │ │ │ -A0B13 Filename Length 0019 (25) │ │ │ │ -A0B15 Extra Length 0018 (24) │ │ │ │ -A0B17 Comment Length 0000 (0) │ │ │ │ -A0B19 Disk Start 0000 (0) │ │ │ │ -A0B1B Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0B1D Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0B21 Local Header Offset 0007847F (492671) │ │ │ │ -A0B25 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0B25: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0B3E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0B40 Length 0005 (5) │ │ │ │ -A0B42 Flags 01 (1) 'Modification' │ │ │ │ -A0B43 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0B47 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0B49 Length 000B (11) │ │ │ │ -A0B4B Version 01 (1) │ │ │ │ -A0B4C UID Size 04 (4) │ │ │ │ -A0B4D UID 00000000 (0) │ │ │ │ -A0B51 GID Size 04 (4) │ │ │ │ -A0B52 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0B56 CENTRAL HEADER #69 02014B50 (33639248) │ │ │ │ -A0B5A Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0B5B Created OS 03 (3) 'Unix' │ │ │ │ -A0B5C Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0B5D Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0B5E General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0B60 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0B62 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0B66 CRC 4FA725C6 (1336354246) │ │ │ │ -A0B6A Compressed Size 00000835 (2101) │ │ │ │ -A0B6E Uncompressed Size 00003384 (13188) │ │ │ │ -A0B72 Filename Length 0011 (17) │ │ │ │ -A0B74 Extra Length 0018 (24) │ │ │ │ -A0B76 Comment Length 0000 (0) │ │ │ │ -A0B78 Disk Start 0000 (0) │ │ │ │ -A0B7A Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0B7C Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0B80 Local Header Offset 0007C3C7 (508871) │ │ │ │ -A0B84 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0B84: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0B95 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0B97 Length 0005 (5) │ │ │ │ -A0B99 Flags 01 (1) 'Modification' │ │ │ │ -A0B9A Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0B9E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0BA0 Length 000B (11) │ │ │ │ -A0BA2 Version 01 (1) │ │ │ │ -A0BA3 UID Size 04 (4) │ │ │ │ -A0BA4 UID 00000000 (0) │ │ │ │ -A0BA8 GID Size 04 (4) │ │ │ │ -A0BA9 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0BAD CENTRAL HEADER #70 02014B50 (33639248) │ │ │ │ -A0BB1 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0BB2 Created OS 03 (3) 'Unix' │ │ │ │ -A0BB3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0BB4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0BB5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0BB7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0BB9 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0BBD CRC 7A11C564 (2047984996) │ │ │ │ -A0BC1 Compressed Size 000051B9 (20921) │ │ │ │ -A0BC5 Uncompressed Size 0001FBE0 (130016) │ │ │ │ -A0BC9 Filename Length 0015 (21) │ │ │ │ -A0BCB Extra Length 0018 (24) │ │ │ │ -A0BCD Comment Length 0000 (0) │ │ │ │ -A0BCF Disk Start 0000 (0) │ │ │ │ -A0BD1 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0BD3 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0BD7 Local Header Offset 0007CC47 (511047) │ │ │ │ -A0BDB Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0BDB: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0BF0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0BF2 Length 0005 (5) │ │ │ │ -A0BF4 Flags 01 (1) 'Modification' │ │ │ │ -A0BF5 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0BF9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0BFB Length 000B (11) │ │ │ │ -A0BFD Version 01 (1) │ │ │ │ -A0BFE UID Size 04 (4) │ │ │ │ -A0BFF UID 00000000 (0) │ │ │ │ -A0C03 GID Size 04 (4) │ │ │ │ -A0C04 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0C08 CENTRAL HEADER #71 02014B50 (33639248) │ │ │ │ -A0C0C Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0C0D Created OS 03 (3) 'Unix' │ │ │ │ -A0C0E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0C0F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0C10 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0C12 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0C14 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0C18 CRC EBD2C2C9 (3956458185) │ │ │ │ -A0C1C Compressed Size 00001B18 (6936) │ │ │ │ -A0C20 Uncompressed Size 00008213 (33299) │ │ │ │ -A0C24 Filename Length 0019 (25) │ │ │ │ -A0C26 Extra Length 0018 (24) │ │ │ │ -A0C28 Comment Length 0000 (0) │ │ │ │ -A0C2A Disk Start 0000 (0) │ │ │ │ -A0C2C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0C2E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0C32 Local Header Offset 00081E4F (532047) │ │ │ │ -A0C36 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0C36: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0C4F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0C51 Length 0005 (5) │ │ │ │ -A0C53 Flags 01 (1) 'Modification' │ │ │ │ -A0C54 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0C58 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0C5A Length 000B (11) │ │ │ │ -A0C5C Version 01 (1) │ │ │ │ -A0C5D UID Size 04 (4) │ │ │ │ -A0C5E UID 00000000 (0) │ │ │ │ -A0C62 GID Size 04 (4) │ │ │ │ -A0C63 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0C67 CENTRAL HEADER #72 02014B50 (33639248) │ │ │ │ -A0C6B Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0C6C Created OS 03 (3) 'Unix' │ │ │ │ -A0C6D Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0C6E Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0C6F General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0C71 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0C73 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0C77 CRC 2EDD972E (786274094) │ │ │ │ -A0C7B Compressed Size 00000D97 (3479) │ │ │ │ -A0C7F Uncompressed Size 00002EA0 (11936) │ │ │ │ -A0C83 Filename Length 0018 (24) │ │ │ │ -A0C85 Extra Length 0018 (24) │ │ │ │ -A0C87 Comment Length 0000 (0) │ │ │ │ -A0C89 Disk Start 0000 (0) │ │ │ │ -A0C8B Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0C8D Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0C91 Local Header Offset 000839BA (539066) │ │ │ │ -A0C95 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0C95: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0CAD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0CAF Length 0005 (5) │ │ │ │ -A0CB1 Flags 01 (1) 'Modification' │ │ │ │ -A0CB2 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0CB6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0CB8 Length 000B (11) │ │ │ │ -A0CBA Version 01 (1) │ │ │ │ -A0CBB UID Size 04 (4) │ │ │ │ -A0CBC UID 00000000 (0) │ │ │ │ -A0CC0 GID Size 04 (4) │ │ │ │ -A0CC1 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0CC5 CENTRAL HEADER #73 02014B50 (33639248) │ │ │ │ -A0CC9 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0CCA Created OS 03 (3) 'Unix' │ │ │ │ -A0CCB Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0CCC Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0CCD General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0CCF Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0CD1 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0CD5 CRC 037E3A9E (58604190) │ │ │ │ -A0CD9 Compressed Size 000001E1 (481) │ │ │ │ -A0CDD Uncompressed Size 00000324 (804) │ │ │ │ -A0CE1 Filename Length 0011 (17) │ │ │ │ -A0CE3 Extra Length 0018 (24) │ │ │ │ -A0CE5 Comment Length 0000 (0) │ │ │ │ -A0CE7 Disk Start 0000 (0) │ │ │ │ -A0CE9 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0CEB Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0CEF Local Header Offset 000847A3 (542627) │ │ │ │ -A0CF3 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0CF3: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0D04 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0D06 Length 0005 (5) │ │ │ │ -A0D08 Flags 01 (1) 'Modification' │ │ │ │ -A0D09 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0D0D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0D0F Length 000B (11) │ │ │ │ -A0D11 Version 01 (1) │ │ │ │ -A0D12 UID Size 04 (4) │ │ │ │ -A0D13 UID 00000000 (0) │ │ │ │ -A0D17 GID Size 04 (4) │ │ │ │ -A0D18 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0D1C CENTRAL HEADER #74 02014B50 (33639248) │ │ │ │ -A0D20 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0D21 Created OS 03 (3) 'Unix' │ │ │ │ -A0D22 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0D23 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0D24 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0D26 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0D28 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0D2C CRC 55187787 (1427666823) │ │ │ │ -A0D30 Compressed Size 000006C3 (1731) │ │ │ │ -A0D34 Uncompressed Size 0000143A (5178) │ │ │ │ -A0D38 Filename Length 0019 (25) │ │ │ │ -A0D3A Extra Length 0018 (24) │ │ │ │ -A0D3C Comment Length 0000 (0) │ │ │ │ -A0D3E Disk Start 0000 (0) │ │ │ │ -A0D40 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0D42 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0D46 Local Header Offset 000849CF (543183) │ │ │ │ -A0D4A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0D4A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0D63 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0D65 Length 0005 (5) │ │ │ │ -A0D67 Flags 01 (1) 'Modification' │ │ │ │ -A0D68 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0D6C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0D6E Length 000B (11) │ │ │ │ -A0D70 Version 01 (1) │ │ │ │ -A0D71 UID Size 04 (4) │ │ │ │ -A0D72 UID 00000000 (0) │ │ │ │ -A0D76 GID Size 04 (4) │ │ │ │ -A0D77 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0D7B CENTRAL HEADER #75 02014B50 (33639248) │ │ │ │ -A0D7F Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0D80 Created OS 03 (3) 'Unix' │ │ │ │ -A0D81 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0D82 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0D83 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0D85 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0D87 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0D8B CRC 2BFA1E54 (737812052) │ │ │ │ -A0D8F Compressed Size 00001EA7 (7847) │ │ │ │ -A0D93 Uncompressed Size 0000CA55 (51797) │ │ │ │ -A0D97 Filename Length 0018 (24) │ │ │ │ -A0D99 Extra Length 0018 (24) │ │ │ │ -A0D9B Comment Length 0000 (0) │ │ │ │ -A0D9D Disk Start 0000 (0) │ │ │ │ -A0D9F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0DA1 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0DA5 Local Header Offset 000850E5 (544997) │ │ │ │ -A0DA9 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0DA9: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0DC1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0DC3 Length 0005 (5) │ │ │ │ -A0DC5 Flags 01 (1) 'Modification' │ │ │ │ -A0DC6 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0DCA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0DCC Length 000B (11) │ │ │ │ -A0DCE Version 01 (1) │ │ │ │ -A0DCF UID Size 04 (4) │ │ │ │ -A0DD0 UID 00000000 (0) │ │ │ │ -A0DD4 GID Size 04 (4) │ │ │ │ -A0DD5 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0DD9 CENTRAL HEADER #76 02014B50 (33639248) │ │ │ │ -A0DDD Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0DDE Created OS 03 (3) 'Unix' │ │ │ │ -A0DDF Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0DE0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0DE1 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0DE3 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0DE5 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0DE9 CRC A14300A7 (2705522855) │ │ │ │ -A0DED Compressed Size 00001C09 (7177) │ │ │ │ -A0DF1 Uncompressed Size 0000C5BE (50622) │ │ │ │ -A0DF5 Filename Length 0012 (18) │ │ │ │ -A0DF7 Extra Length 0018 (24) │ │ │ │ -A0DF9 Comment Length 0000 (0) │ │ │ │ -A0DFB Disk Start 0000 (0) │ │ │ │ -A0DFD Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0DFF Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0E03 Local Header Offset 00086FDE (552926) │ │ │ │ -A0E07 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0E07: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0E19 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0E1B Length 0005 (5) │ │ │ │ -A0E1D Flags 01 (1) 'Modification' │ │ │ │ -A0E1E Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0E22 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0E24 Length 000B (11) │ │ │ │ -A0E26 Version 01 (1) │ │ │ │ -A0E27 UID Size 04 (4) │ │ │ │ -A0E28 UID 00000000 (0) │ │ │ │ -A0E2C GID Size 04 (4) │ │ │ │ -A0E2D GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0E31 CENTRAL HEADER #77 02014B50 (33639248) │ │ │ │ -A0E35 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0E36 Created OS 03 (3) 'Unix' │ │ │ │ -A0E37 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0E38 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0E39 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0E3B Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0E3D Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0E41 CRC EAB45C58 (3937688664) │ │ │ │ -A0E45 Compressed Size 00001E17 (7703) │ │ │ │ -A0E49 Uncompressed Size 000087D8 (34776) │ │ │ │ -A0E4D Filename Length 0016 (22) │ │ │ │ -A0E4F Extra Length 0018 (24) │ │ │ │ -A0E51 Comment Length 0000 (0) │ │ │ │ -A0E53 Disk Start 0000 (0) │ │ │ │ -A0E55 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0E57 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0E5B Local Header Offset 00088C33 (560179) │ │ │ │ -A0E5F Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0E5F: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0E75 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0E77 Length 0005 (5) │ │ │ │ -A0E79 Flags 01 (1) 'Modification' │ │ │ │ -A0E7A Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0E7E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0E80 Length 000B (11) │ │ │ │ -A0E82 Version 01 (1) │ │ │ │ -A0E83 UID Size 04 (4) │ │ │ │ -A0E84 UID 00000000 (0) │ │ │ │ -A0E88 GID Size 04 (4) │ │ │ │ -A0E89 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0E8D CENTRAL HEADER #78 02014B50 (33639248) │ │ │ │ -A0E91 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0E92 Created OS 03 (3) 'Unix' │ │ │ │ -A0E93 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0E94 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0E95 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0E97 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0E99 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0E9D CRC DF098402 (3741942786) │ │ │ │ -A0EA1 Compressed Size 000029A9 (10665) │ │ │ │ -A0EA5 Uncompressed Size 0000D039 (53305) │ │ │ │ -A0EA9 Filename Length 001A (26) │ │ │ │ -A0EAB Extra Length 0018 (24) │ │ │ │ -A0EAD Comment Length 0000 (0) │ │ │ │ -A0EAF Disk Start 0000 (0) │ │ │ │ -A0EB1 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0EB3 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0EB7 Local Header Offset 0008AA9A (567962) │ │ │ │ -A0EBB Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0EBB: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0ED5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0ED7 Length 0005 (5) │ │ │ │ -A0ED9 Flags 01 (1) 'Modification' │ │ │ │ -A0EDA Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0EDE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0EE0 Length 000B (11) │ │ │ │ -A0EE2 Version 01 (1) │ │ │ │ -A0EE3 UID Size 04 (4) │ │ │ │ -A0EE4 UID 00000000 (0) │ │ │ │ -A0EE8 GID Size 04 (4) │ │ │ │ -A0EE9 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0EED CENTRAL HEADER #79 02014B50 (33639248) │ │ │ │ -A0EF1 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0EF2 Created OS 03 (3) 'Unix' │ │ │ │ -A0EF3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0EF4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0EF5 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0EF7 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0EF9 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0EFD CRC 843B749E (2218488990) │ │ │ │ -A0F01 Compressed Size 000009AD (2477) │ │ │ │ -A0F05 Uncompressed Size 00001DB7 (7607) │ │ │ │ -A0F09 Filename Length 0018 (24) │ │ │ │ -A0F0B Extra Length 0018 (24) │ │ │ │ -A0F0D Comment Length 0000 (0) │ │ │ │ -A0F0F Disk Start 0000 (0) │ │ │ │ -A0F11 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0F13 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0F17 Local Header Offset 0008D497 (578711) │ │ │ │ -A0F1B Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0F1B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0F33 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0F35 Length 0005 (5) │ │ │ │ -A0F37 Flags 01 (1) 'Modification' │ │ │ │ -A0F38 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0F3C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0F3E Length 000B (11) │ │ │ │ -A0F40 Version 01 (1) │ │ │ │ -A0F41 UID Size 04 (4) │ │ │ │ -A0F42 UID 00000000 (0) │ │ │ │ -A0F46 GID Size 04 (4) │ │ │ │ -A0F47 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0F4B CENTRAL HEADER #80 02014B50 (33639248) │ │ │ │ -A0F4F Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0F50 Created OS 03 (3) 'Unix' │ │ │ │ -A0F51 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0F52 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0F53 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0F55 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0F57 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0F5B CRC F5E2129F (4125233823) │ │ │ │ -A0F5F Compressed Size 000016BC (5820) │ │ │ │ -A0F63 Uncompressed Size 000016CD (5837) │ │ │ │ -A0F67 Filename Length 0015 (21) │ │ │ │ -A0F69 Extra Length 0018 (24) │ │ │ │ -A0F6B Comment Length 0000 (0) │ │ │ │ -A0F6D Disk Start 0000 (0) │ │ │ │ -A0F6F Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0F71 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0F75 Local Header Offset 0008DE96 (581270) │ │ │ │ -A0F79 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0F79: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0F8E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0F90 Length 0005 (5) │ │ │ │ -A0F92 Flags 01 (1) 'Modification' │ │ │ │ -A0F93 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0F97 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0F99 Length 000B (11) │ │ │ │ -A0F9B Version 01 (1) │ │ │ │ -A0F9C UID Size 04 (4) │ │ │ │ -A0F9D UID 00000000 (0) │ │ │ │ -A0FA1 GID Size 04 (4) │ │ │ │ -A0FA2 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A0FA6 CENTRAL HEADER #81 02014B50 (33639248) │ │ │ │ -A0FAA Created Zip Spec 3D (61) '6.1' │ │ │ │ -A0FAB Created OS 03 (3) 'Unix' │ │ │ │ -A0FAC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A0FAD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A0FAE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A0FB0 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A0FB2 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0FB6 CRC F5E2129F (4125233823) │ │ │ │ -A0FBA Compressed Size 000016BC (5820) │ │ │ │ -A0FBE Uncompressed Size 000016CD (5837) │ │ │ │ -A0FC2 Filename Length 001C (28) │ │ │ │ -A0FC4 Extra Length 0018 (24) │ │ │ │ -A0FC6 Comment Length 0000 (0) │ │ │ │ -A0FC8 Disk Start 0000 (0) │ │ │ │ -A0FCA Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A0FCC Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A0FD0 Local Header Offset 0008F5A1 (587169) │ │ │ │ -A0FD4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA0FD4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A0FF0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A0FF2 Length 0005 (5) │ │ │ │ -A0FF4 Flags 01 (1) 'Modification' │ │ │ │ -A0FF5 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A0FF9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A0FFB Length 000B (11) │ │ │ │ -A0FFD Version 01 (1) │ │ │ │ -A0FFE UID Size 04 (4) │ │ │ │ -A0FFF UID 00000000 (0) │ │ │ │ -A1003 GID Size 04 (4) │ │ │ │ -A1004 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A1008 CENTRAL HEADER #82 02014B50 (33639248) │ │ │ │ -A100C Created Zip Spec 3D (61) '6.1' │ │ │ │ -A100D Created OS 03 (3) 'Unix' │ │ │ │ -A100E Extract Zip Spec 0A (10) '1.0' │ │ │ │ -A100F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A1010 General Purpose Flag 0000 (0) │ │ │ │ -A1012 Compression Method 0000 (0) 'Stored' │ │ │ │ -A1014 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A1018 CRC FC95F24B (4237685323) │ │ │ │ -A101C Compressed Size 00001B84 (7044) │ │ │ │ -A1020 Uncompressed Size 00001B84 (7044) │ │ │ │ -A1024 Filename Length 0016 (22) │ │ │ │ -A1026 Extra Length 0018 (24) │ │ │ │ -A1028 Comment Length 0000 (0) │ │ │ │ -A102A Disk Start 0000 (0) │ │ │ │ -A102C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A102E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A1032 Local Header Offset 00090CB3 (593075) │ │ │ │ -A1036 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA1036: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A104C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A104E Length 0005 (5) │ │ │ │ -A1050 Flags 01 (1) 'Modification' │ │ │ │ -A1051 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A1055 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A1057 Length 000B (11) │ │ │ │ -A1059 Version 01 (1) │ │ │ │ -A105A UID Size 04 (4) │ │ │ │ -A105B UID 00000000 (0) │ │ │ │ -A105F GID Size 04 (4) │ │ │ │ -A1060 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A1064 CENTRAL HEADER #83 02014B50 (33639248) │ │ │ │ -A1068 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A1069 Created OS 03 (3) 'Unix' │ │ │ │ -A106A Extract Zip Spec 0A (10) '1.0' │ │ │ │ -A106B Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A106C General Purpose Flag 0000 (0) │ │ │ │ -A106E Compression Method 0000 (0) 'Stored' │ │ │ │ -A1070 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A1074 CRC D0D71F86 (3503759238) │ │ │ │ -A1078 Compressed Size 00000B7B (2939) │ │ │ │ -A107C Uncompressed Size 00000B7B (2939) │ │ │ │ -A1080 Filename Length 0016 (22) │ │ │ │ -A1082 Extra Length 0018 (24) │ │ │ │ -A1084 Comment Length 0000 (0) │ │ │ │ -A1086 Disk Start 0000 (0) │ │ │ │ -A1088 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A108A Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A108E Local Header Offset 00092887 (600199) │ │ │ │ -A1092 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA1092: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A10A8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A10AA Length 0005 (5) │ │ │ │ -A10AC Flags 01 (1) 'Modification' │ │ │ │ -A10AD Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A10B1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A10B3 Length 000B (11) │ │ │ │ -A10B5 Version 01 (1) │ │ │ │ -A10B6 UID Size 04 (4) │ │ │ │ -A10B7 UID 00000000 (0) │ │ │ │ -A10BB GID Size 04 (4) │ │ │ │ -A10BC GID 00000000 (0) │ │ │ │ - │ │ │ │ -A10C0 CENTRAL HEADER #84 02014B50 (33639248) │ │ │ │ -A10C4 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A10C5 Created OS 03 (3) 'Unix' │ │ │ │ -A10C6 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -A10C7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A10C8 General Purpose Flag 0000 (0) │ │ │ │ -A10CA Compression Method 0000 (0) 'Stored' │ │ │ │ -A10CC Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A10D0 CRC FFF9C4D2 (4294558930) │ │ │ │ -A10D4 Compressed Size 0000138F (5007) │ │ │ │ -A10D8 Uncompressed Size 0000138F (5007) │ │ │ │ -A10DC Filename Length 0016 (22) │ │ │ │ -A10DE Extra Length 0018 (24) │ │ │ │ -A10E0 Comment Length 0000 (0) │ │ │ │ -A10E2 Disk Start 0000 (0) │ │ │ │ -A10E4 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A10E6 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A10EA Local Header Offset 00093452 (603218) │ │ │ │ -A10EE Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA10EE: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A1104 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A1106 Length 0005 (5) │ │ │ │ -A1108 Flags 01 (1) 'Modification' │ │ │ │ -A1109 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A110D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A110F Length 000B (11) │ │ │ │ -A1111 Version 01 (1) │ │ │ │ -A1112 UID Size 04 (4) │ │ │ │ -A1113 UID 00000000 (0) │ │ │ │ -A1117 GID Size 04 (4) │ │ │ │ -A1118 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A111C CENTRAL HEADER #85 02014B50 (33639248) │ │ │ │ -A1120 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A1121 Created OS 03 (3) 'Unix' │ │ │ │ -A1122 Extract Zip Spec 0A (10) '1.0' │ │ │ │ -A1123 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A1124 General Purpose Flag 0000 (0) │ │ │ │ -A1126 Compression Method 0000 (0) 'Stored' │ │ │ │ -A1128 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A112C CRC A1037E8E (2701360782) │ │ │ │ -A1130 Compressed Size 0000145E (5214) │ │ │ │ -A1134 Uncompressed Size 0000145E (5214) │ │ │ │ -A1138 Filename Length 0016 (22) │ │ │ │ -A113A Extra Length 0018 (24) │ │ │ │ -A113C Comment Length 0000 (0) │ │ │ │ -A113E Disk Start 0000 (0) │ │ │ │ -A1140 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A1142 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A1146 Local Header Offset 00094831 (608305) │ │ │ │ -A114A Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA114A: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A1160 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A1162 Length 0005 (5) │ │ │ │ -A1164 Flags 01 (1) 'Modification' │ │ │ │ -A1165 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A1169 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A116B Length 000B (11) │ │ │ │ -A116D Version 01 (1) │ │ │ │ -A116E UID Size 04 (4) │ │ │ │ -A116F UID 00000000 (0) │ │ │ │ -A1173 GID Size 04 (4) │ │ │ │ -A1174 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A1178 CENTRAL HEADER #86 02014B50 (33639248) │ │ │ │ -A117C Created Zip Spec 3D (61) '6.1' │ │ │ │ -A117D Created OS 03 (3) 'Unix' │ │ │ │ -A117E Extract Zip Spec 0A (10) '1.0' │ │ │ │ -A117F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A1180 General Purpose Flag 0000 (0) │ │ │ │ -A1182 Compression Method 0000 (0) 'Stored' │ │ │ │ -A1184 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A1188 CRC 5E9E64F1 (1587438833) │ │ │ │ -A118C Compressed Size 000008EC (2284) │ │ │ │ -A1190 Uncompressed Size 000008EC (2284) │ │ │ │ -A1194 Filename Length 0016 (22) │ │ │ │ -A1196 Extra Length 0018 (24) │ │ │ │ -A1198 Comment Length 0000 (0) │ │ │ │ -A119A Disk Start 0000 (0) │ │ │ │ -A119C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A119E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A11A2 Local Header Offset 00095CDF (613599) │ │ │ │ -A11A6 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA11A6: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A11BC Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A11BE Length 0005 (5) │ │ │ │ -A11C0 Flags 01 (1) 'Modification' │ │ │ │ -A11C1 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A11C5 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A11C7 Length 000B (11) │ │ │ │ -A11C9 Version 01 (1) │ │ │ │ -A11CA UID Size 04 (4) │ │ │ │ -A11CB UID 00000000 (0) │ │ │ │ -A11CF GID Size 04 (4) │ │ │ │ -A11D0 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A11D4 CENTRAL HEADER #87 02014B50 (33639248) │ │ │ │ -A11D8 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A11D9 Created OS 03 (3) 'Unix' │ │ │ │ -A11DA Extract Zip Spec 0A (10) '1.0' │ │ │ │ -A11DB Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A11DC General Purpose Flag 0000 (0) │ │ │ │ -A11DE Compression Method 0000 (0) 'Stored' │ │ │ │ -A11E0 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A11E4 CRC 42E340AB (1122189483) │ │ │ │ -A11E8 Compressed Size 00001F2E (7982) │ │ │ │ -A11EC Uncompressed Size 00001F2E (7982) │ │ │ │ -A11F0 Filename Length 001E (30) │ │ │ │ -A11F2 Extra Length 0018 (24) │ │ │ │ -A11F4 Comment Length 0000 (0) │ │ │ │ -A11F6 Disk Start 0000 (0) │ │ │ │ -A11F8 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A11FA Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A11FE Local Header Offset 0009661B (615963) │ │ │ │ -A1202 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA1202: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A1220 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A1222 Length 0005 (5) │ │ │ │ -A1224 Flags 01 (1) 'Modification' │ │ │ │ -A1225 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A1229 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A122B Length 000B (11) │ │ │ │ -A122D Version 01 (1) │ │ │ │ -A122E UID Size 04 (4) │ │ │ │ -A122F UID 00000000 (0) │ │ │ │ -A1233 GID Size 04 (4) │ │ │ │ -A1234 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A1238 CENTRAL HEADER #88 02014B50 (33639248) │ │ │ │ -A123C Created Zip Spec 3D (61) '6.1' │ │ │ │ -A123D Created OS 03 (3) 'Unix' │ │ │ │ -A123E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A123F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A1240 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A1242 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A1244 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A1248 CRC 5E60F644 (1583412804) │ │ │ │ -A124C Compressed Size 000040A4 (16548) │ │ │ │ -A1250 Uncompressed Size 0001964C (104012) │ │ │ │ -A1254 Filename Length 001A (26) │ │ │ │ -A1256 Extra Length 0018 (24) │ │ │ │ -A1258 Comment Length 0000 (0) │ │ │ │ -A125A Disk Start 0000 (0) │ │ │ │ -A125C Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A125E Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A1262 Local Header Offset 000985A1 (624033) │ │ │ │ -A1266 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA1266: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A1280 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A1282 Length 0005 (5) │ │ │ │ -A1284 Flags 01 (1) 'Modification' │ │ │ │ -A1285 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A1289 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A128B Length 000B (11) │ │ │ │ -A128D Version 01 (1) │ │ │ │ -A128E UID Size 04 (4) │ │ │ │ -A128F UID 00000000 (0) │ │ │ │ -A1293 GID Size 04 (4) │ │ │ │ -A1294 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A1298 CENTRAL HEADER #89 02014B50 (33639248) │ │ │ │ -A129C Created Zip Spec 3D (61) '6.1' │ │ │ │ -A129D Created OS 03 (3) 'Unix' │ │ │ │ -A129E Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A129F Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A12A0 General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A12A2 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A12A4 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A12A8 CRC 20172B93 (538389395) │ │ │ │ -A12AC Compressed Size 000029C9 (10697) │ │ │ │ -A12B0 Uncompressed Size 0000BAF5 (47861) │ │ │ │ -A12B4 Filename Length 0018 (24) │ │ │ │ -A12B6 Extra Length 0018 (24) │ │ │ │ -A12B8 Comment Length 0000 (0) │ │ │ │ -A12BA Disk Start 0000 (0) │ │ │ │ -A12BC Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A12BE Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A12C2 Local Header Offset 0009C699 (640665) │ │ │ │ -A12C6 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA12C6: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A12DE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A12E0 Length 0005 (5) │ │ │ │ -A12E2 Flags 01 (1) 'Modification' │ │ │ │ -A12E3 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A12E7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A12E9 Length 000B (11) │ │ │ │ -A12EB Version 01 (1) │ │ │ │ -A12EC UID Size 04 (4) │ │ │ │ -A12ED UID 00000000 (0) │ │ │ │ -A12F1 GID Size 04 (4) │ │ │ │ -A12F2 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A12F6 CENTRAL HEADER #90 02014B50 (33639248) │ │ │ │ -A12FA Created Zip Spec 3D (61) '6.1' │ │ │ │ -A12FB Created OS 03 (3) 'Unix' │ │ │ │ -A12FC Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A12FD Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A12FE General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A1300 Compression Method 0008 (8) 'Deflated' │ │ │ │ -A1302 Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A1306 CRC DCB3B516 (3702764822) │ │ │ │ -A130A Compressed Size 000000AE (174) │ │ │ │ -A130E Uncompressed Size 000000FC (252) │ │ │ │ -A1312 Filename Length 0016 (22) │ │ │ │ -A1314 Extra Length 0018 (24) │ │ │ │ -A1316 Comment Length 0000 (0) │ │ │ │ -A1318 Disk Start 0000 (0) │ │ │ │ -A131A Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A131C Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A1320 Local Header Offset 0009F0B4 (651444) │ │ │ │ -A1324 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA1324: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A133A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A133C Length 0005 (5) │ │ │ │ -A133E Flags 01 (1) 'Modification' │ │ │ │ -A133F Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A1343 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A1345 Length 000B (11) │ │ │ │ -A1347 Version 01 (1) │ │ │ │ -A1348 UID Size 04 (4) │ │ │ │ -A1349 UID 00000000 (0) │ │ │ │ -A134D GID Size 04 (4) │ │ │ │ -A134E GID 00000000 (0) │ │ │ │ - │ │ │ │ -A1352 CENTRAL HEADER #91 02014B50 (33639248) │ │ │ │ -A1356 Created Zip Spec 3D (61) '6.1' │ │ │ │ -A1357 Created OS 03 (3) 'Unix' │ │ │ │ -A1358 Extract Zip Spec 14 (20) '2.0' │ │ │ │ -A1359 Extract OS 00 (0) 'MS-DOS' │ │ │ │ -A135A General Purpose Flag 0000 (0) │ │ │ │ - [Bits 1-2] 0 'Normal Compression' │ │ │ │ -A135C Compression Method 0008 (8) 'Deflated' │ │ │ │ -A135E Modification Time 5CB16ACB (1555131083) 'Sun May 17 13:22:22 2026' │ │ │ │ -A1362 CRC 58439733 (1480824627) │ │ │ │ -A1366 Compressed Size 00000077 (119) │ │ │ │ -A136A Uncompressed Size 000000A2 (162) │ │ │ │ -A136E Filename Length 002D (45) │ │ │ │ -A1370 Extra Length 0018 (24) │ │ │ │ -A1372 Comment Length 0000 (0) │ │ │ │ -A1374 Disk Start 0000 (0) │ │ │ │ -A1376 Int File Attributes 0000 (0) │ │ │ │ - [Bit 0] 0 'Binary Data' │ │ │ │ -A1378 Ext File Attributes 01A40000 (27525120) │ │ │ │ - [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ -A137C Local Header Offset 0009F1B2 (651698) │ │ │ │ -A1380 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# │ │ │ │ -# WARNING: Offset 0xA1380: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ -# Zero length filename │ │ │ │ -# │ │ │ │ -A13AD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ -A13AF Length 0005 (5) │ │ │ │ -A13B1 Flags 01 (1) 'Modification' │ │ │ │ -A13B2 Modification Time 6A09C10E (1779024142) 'Sun May 17 13:22:22 2026' │ │ │ │ -A13B6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ -A13B8 Length 000B (11) │ │ │ │ -A13BA Version 01 (1) │ │ │ │ -A13BB UID Size 04 (4) │ │ │ │ -A13BC UID 00000000 (0) │ │ │ │ -A13C0 GID Size 04 (4) │ │ │ │ -A13C1 GID 00000000 (0) │ │ │ │ - │ │ │ │ -A13C5 END CENTRAL HEADER 06054B50 (101010256) │ │ │ │ -A13C9 Number of this disk 0000 (0) │ │ │ │ -A13CB Central Dir Disk no 0000 (0) │ │ │ │ -A13CD Entries in this disk 005B (91) │ │ │ │ -A13CF Total Entries 005B (91) │ │ │ │ -A13D1 Size of Central Dir 00002135 (8501) │ │ │ │ -A13D5 Offset to Central Dir 0009F290 (651920) │ │ │ │ -A13D9 Comment Length 0000 (0) │ │ │ │ +9F1BA LOCAL HEADER #91 04034B50 (67324752) │ │ │ │ +9F1BE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F1BF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F1C0 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F1C2 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F1C4 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F1C8 CRC 58439733 (1480824627) │ │ │ │ +9F1CC Compressed Size 00000077 (119) │ │ │ │ +9F1D0 Uncompressed Size 000000A2 (162) │ │ │ │ +9F1D4 Filename Length 002D (45) │ │ │ │ +9F1D6 Extra Length 001C (28) │ │ │ │ +9F1D8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F1D8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F205 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F207 Length 0009 (9) │ │ │ │ +9F209 Flags 03 (3) 'Modification Access' │ │ │ │ +9F20A Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F20E Access Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F212 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F214 Length 000B (11) │ │ │ │ +9F216 Version 01 (1) │ │ │ │ +9F217 UID Size 04 (4) │ │ │ │ +9F218 UID 00000000 (0) │ │ │ │ +9F21C GID Size 04 (4) │ │ │ │ +9F21D GID 00000000 (0) │ │ │ │ +9F221 PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX │ │ │ │ + │ │ │ │ +9F298 CENTRAL HEADER #1 02014B50 (33639248) │ │ │ │ +9F29C Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F29D Created OS 03 (3) 'Unix' │ │ │ │ +9F29E Extract Zip Spec 0A (10) '1.0' │ │ │ │ +9F29F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F2A0 General Purpose Flag 0000 (0) │ │ │ │ +9F2A2 Compression Method 0000 (0) 'Stored' │ │ │ │ +9F2A4 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F2A8 CRC 2CAB616F (749429103) │ │ │ │ +9F2AC Compressed Size 00000014 (20) │ │ │ │ +9F2B0 Uncompressed Size 00000014 (20) │ │ │ │ +9F2B4 Filename Length 0008 (8) │ │ │ │ +9F2B6 Extra Length 0018 (24) │ │ │ │ +9F2B8 Comment Length 0000 (0) │ │ │ │ +9F2BA Disk Start 0000 (0) │ │ │ │ +9F2BC Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F2BE Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F2C2 Local Header Offset 00000000 (0) │ │ │ │ +9F2C6 Filename 'XXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F2C6: Filename 'XXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F2CE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F2D0 Length 0005 (5) │ │ │ │ +9F2D2 Flags 01 (1) 'Modification' │ │ │ │ +9F2D3 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F2D7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F2D9 Length 000B (11) │ │ │ │ +9F2DB Version 01 (1) │ │ │ │ +9F2DC UID Size 04 (4) │ │ │ │ +9F2DD UID 00000000 (0) │ │ │ │ +9F2E1 GID Size 04 (4) │ │ │ │ +9F2E2 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F2E6 CENTRAL HEADER #2 02014B50 (33639248) │ │ │ │ +9F2EA Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F2EB Created OS 03 (3) 'Unix' │ │ │ │ +9F2EC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F2ED Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F2EE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F2F0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F2F2 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F2F6 CRC 7CFCE72A (2096949034) │ │ │ │ +9F2FA Compressed Size 000015AD (5549) │ │ │ │ +9F2FE Uncompressed Size 00004603 (17923) │ │ │ │ +9F302 Filename Length 0014 (20) │ │ │ │ +9F304 Extra Length 0018 (24) │ │ │ │ +9F306 Comment Length 0000 (0) │ │ │ │ +9F308 Disk Start 0000 (0) │ │ │ │ +9F30A Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F30C Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F310 Local Header Offset 00000056 (86) │ │ │ │ +9F314 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F314: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F328 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F32A Length 0005 (5) │ │ │ │ +9F32C Flags 01 (1) 'Modification' │ │ │ │ +9F32D Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F331 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F333 Length 000B (11) │ │ │ │ +9F335 Version 01 (1) │ │ │ │ +9F336 UID Size 04 (4) │ │ │ │ +9F337 UID 00000000 (0) │ │ │ │ +9F33B GID Size 04 (4) │ │ │ │ +9F33C GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F340 CENTRAL HEADER #3 02014B50 (33639248) │ │ │ │ +9F344 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F345 Created OS 03 (3) 'Unix' │ │ │ │ +9F346 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F347 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F348 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F34A Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F34C Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F350 CRC 741FDE60 (1948245600) │ │ │ │ +9F354 Compressed Size 000006D6 (1750) │ │ │ │ +9F358 Uncompressed Size 00001242 (4674) │ │ │ │ +9F35C Filename Length 0013 (19) │ │ │ │ +9F35E Extra Length 0018 (24) │ │ │ │ +9F360 Comment Length 0000 (0) │ │ │ │ +9F362 Disk Start 0000 (0) │ │ │ │ +9F364 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F366 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F36A Local Header Offset 00001651 (5713) │ │ │ │ +9F36E Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F36E: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F381 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F383 Length 0005 (5) │ │ │ │ +9F385 Flags 01 (1) 'Modification' │ │ │ │ +9F386 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F38A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F38C Length 000B (11) │ │ │ │ +9F38E Version 01 (1) │ │ │ │ +9F38F UID Size 04 (4) │ │ │ │ +9F390 UID 00000000 (0) │ │ │ │ +9F394 GID Size 04 (4) │ │ │ │ +9F395 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F399 CENTRAL HEADER #4 02014B50 (33639248) │ │ │ │ +9F39D Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F39E Created OS 03 (3) 'Unix' │ │ │ │ +9F39F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F3A0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F3A1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F3A3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F3A5 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F3A9 CRC 6F43BBC2 (1866709954) │ │ │ │ +9F3AD Compressed Size 00002DAB (11691) │ │ │ │ +9F3B1 Uncompressed Size 0000D0C2 (53442) │ │ │ │ +9F3B5 Filename Length 0014 (20) │ │ │ │ +9F3B7 Extra Length 0018 (24) │ │ │ │ +9F3B9 Comment Length 0000 (0) │ │ │ │ +9F3BB Disk Start 0000 (0) │ │ │ │ +9F3BD Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F3BF Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F3C3 Local Header Offset 00001D74 (7540) │ │ │ │ +9F3C7 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F3C7: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F3DB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F3DD Length 0005 (5) │ │ │ │ +9F3DF Flags 01 (1) 'Modification' │ │ │ │ +9F3E0 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F3E4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F3E6 Length 000B (11) │ │ │ │ +9F3E8 Version 01 (1) │ │ │ │ +9F3E9 UID Size 04 (4) │ │ │ │ +9F3EA UID 00000000 (0) │ │ │ │ +9F3EE GID Size 04 (4) │ │ │ │ +9F3EF GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F3F3 CENTRAL HEADER #5 02014B50 (33639248) │ │ │ │ +9F3F7 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F3F8 Created OS 03 (3) 'Unix' │ │ │ │ +9F3F9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F3FA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F3FB General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F3FD Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F3FF Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F403 CRC AFEB34EA (2951427306) │ │ │ │ +9F407 Compressed Size 000003F0 (1008) │ │ │ │ +9F40B Uncompressed Size 00000877 (2167) │ │ │ │ +9F40F Filename Length 0014 (20) │ │ │ │ +9F411 Extra Length 0018 (24) │ │ │ │ +9F413 Comment Length 0000 (0) │ │ │ │ +9F415 Disk Start 0000 (0) │ │ │ │ +9F417 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F419 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F41D Local Header Offset 00004B6D (19309) │ │ │ │ +9F421 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F421: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F435 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F437 Length 0005 (5) │ │ │ │ +9F439 Flags 01 (1) 'Modification' │ │ │ │ +9F43A Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F43E Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F440 Length 000B (11) │ │ │ │ +9F442 Version 01 (1) │ │ │ │ +9F443 UID Size 04 (4) │ │ │ │ +9F444 UID 00000000 (0) │ │ │ │ +9F448 GID Size 04 (4) │ │ │ │ +9F449 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F44D CENTRAL HEADER #6 02014B50 (33639248) │ │ │ │ +9F451 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F452 Created OS 03 (3) 'Unix' │ │ │ │ +9F453 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F454 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F455 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F457 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F459 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F45D CRC 21AF85A7 (565151143) │ │ │ │ +9F461 Compressed Size 000001AE (430) │ │ │ │ +9F465 Uncompressed Size 000002FE (766) │ │ │ │ +9F469 Filename Length 0011 (17) │ │ │ │ +9F46B Extra Length 0018 (24) │ │ │ │ +9F46D Comment Length 0000 (0) │ │ │ │ +9F46F Disk Start 0000 (0) │ │ │ │ +9F471 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F473 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F477 Local Header Offset 00004FAB (20395) │ │ │ │ +9F47B Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F47B: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F48C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F48E Length 0005 (5) │ │ │ │ +9F490 Flags 01 (1) 'Modification' │ │ │ │ +9F491 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F495 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F497 Length 000B (11) │ │ │ │ +9F499 Version 01 (1) │ │ │ │ +9F49A UID Size 04 (4) │ │ │ │ +9F49B UID 00000000 (0) │ │ │ │ +9F49F GID Size 04 (4) │ │ │ │ +9F4A0 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F4A4 CENTRAL HEADER #7 02014B50 (33639248) │ │ │ │ +9F4A8 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F4A9 Created OS 03 (3) 'Unix' │ │ │ │ +9F4AA Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F4AB Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F4AC General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F4AE Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F4B0 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F4B4 CRC D70AFB1E (3607821086) │ │ │ │ +9F4B8 Compressed Size 000020BF (8383) │ │ │ │ +9F4BC Uncompressed Size 0000B4AC (46252) │ │ │ │ +9F4C0 Filename Length 001B (27) │ │ │ │ +9F4C2 Extra Length 0018 (24) │ │ │ │ +9F4C4 Comment Length 0000 (0) │ │ │ │ +9F4C6 Disk Start 0000 (0) │ │ │ │ +9F4C8 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F4CA Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F4CE Local Header Offset 000051A4 (20900) │ │ │ │ +9F4D2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F4D2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F4ED Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F4EF Length 0005 (5) │ │ │ │ +9F4F1 Flags 01 (1) 'Modification' │ │ │ │ +9F4F2 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F4F6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F4F8 Length 000B (11) │ │ │ │ +9F4FA Version 01 (1) │ │ │ │ +9F4FB UID Size 04 (4) │ │ │ │ +9F4FC UID 00000000 (0) │ │ │ │ +9F500 GID Size 04 (4) │ │ │ │ +9F501 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F505 CENTRAL HEADER #8 02014B50 (33639248) │ │ │ │ +9F509 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F50A Created OS 03 (3) 'Unix' │ │ │ │ +9F50B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F50C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F50D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F50F Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F511 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F515 CRC 2D371F1E (758587166) │ │ │ │ +9F519 Compressed Size 00000E70 (3696) │ │ │ │ +9F51D Uncompressed Size 000030B3 (12467) │ │ │ │ +9F521 Filename Length 001D (29) │ │ │ │ +9F523 Extra Length 0018 (24) │ │ │ │ +9F525 Comment Length 0000 (0) │ │ │ │ +9F527 Disk Start 0000 (0) │ │ │ │ +9F529 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F52B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F52F Local Header Offset 000072B8 (29368) │ │ │ │ +9F533 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F533: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F550 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F552 Length 0005 (5) │ │ │ │ +9F554 Flags 01 (1) 'Modification' │ │ │ │ +9F555 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F559 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F55B Length 000B (11) │ │ │ │ +9F55D Version 01 (1) │ │ │ │ +9F55E UID Size 04 (4) │ │ │ │ +9F55F UID 00000000 (0) │ │ │ │ +9F563 GID Size 04 (4) │ │ │ │ +9F564 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F568 CENTRAL HEADER #9 02014B50 (33639248) │ │ │ │ +9F56C Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F56D Created OS 03 (3) 'Unix' │ │ │ │ +9F56E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F56F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F570 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F572 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F574 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F578 CRC 7903D0CB (2030293195) │ │ │ │ +9F57C Compressed Size 00000973 (2419) │ │ │ │ +9F580 Uncompressed Size 00001CB3 (7347) │ │ │ │ +9F584 Filename Length 0019 (25) │ │ │ │ +9F586 Extra Length 0018 (24) │ │ │ │ +9F588 Comment Length 0000 (0) │ │ │ │ +9F58A Disk Start 0000 (0) │ │ │ │ +9F58C Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F58E Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F592 Local Header Offset 0000817F (33151) │ │ │ │ +9F596 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F596: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F5AF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F5B1 Length 0005 (5) │ │ │ │ +9F5B3 Flags 01 (1) 'Modification' │ │ │ │ +9F5B4 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F5B8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F5BA Length 000B (11) │ │ │ │ +9F5BC Version 01 (1) │ │ │ │ +9F5BD UID Size 04 (4) │ │ │ │ +9F5BE UID 00000000 (0) │ │ │ │ +9F5C2 GID Size 04 (4) │ │ │ │ +9F5C3 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F5C7 CENTRAL HEADER #10 02014B50 (33639248) │ │ │ │ +9F5CB Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F5CC Created OS 03 (3) 'Unix' │ │ │ │ +9F5CD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F5CE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F5CF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F5D1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F5D3 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F5D7 CRC 6D2A31E6 (1831481830) │ │ │ │ +9F5DB Compressed Size 00003866 (14438) │ │ │ │ +9F5DF Uncompressed Size 0000F7B0 (63408) │ │ │ │ +9F5E3 Filename Length 0015 (21) │ │ │ │ +9F5E5 Extra Length 0018 (24) │ │ │ │ +9F5E7 Comment Length 0000 (0) │ │ │ │ +9F5E9 Disk Start 0000 (0) │ │ │ │ +9F5EB Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F5ED Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F5F1 Local Header Offset 00008B45 (35653) │ │ │ │ +9F5F5 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F5F5: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F60A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F60C Length 0005 (5) │ │ │ │ +9F60E Flags 01 (1) 'Modification' │ │ │ │ +9F60F Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F613 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F615 Length 000B (11) │ │ │ │ +9F617 Version 01 (1) │ │ │ │ +9F618 UID Size 04 (4) │ │ │ │ +9F619 UID 00000000 (0) │ │ │ │ +9F61D GID Size 04 (4) │ │ │ │ +9F61E GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F622 CENTRAL HEADER #11 02014B50 (33639248) │ │ │ │ +9F626 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F627 Created OS 03 (3) 'Unix' │ │ │ │ +9F628 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F629 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F62A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F62C Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F62E Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F632 CRC 1D369EB9 (490118841) │ │ │ │ +9F636 Compressed Size 0000A9BC (43452) │ │ │ │ +9F63A Uncompressed Size 0003D7D6 (251862) │ │ │ │ +9F63E Filename Length 0012 (18) │ │ │ │ +9F640 Extra Length 0018 (24) │ │ │ │ +9F642 Comment Length 0000 (0) │ │ │ │ +9F644 Disk Start 0000 (0) │ │ │ │ +9F646 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F648 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F64C Local Header Offset 0000C3FA (50170) │ │ │ │ +9F650 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F650: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F662 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F664 Length 0005 (5) │ │ │ │ +9F666 Flags 01 (1) 'Modification' │ │ │ │ +9F667 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F66B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F66D Length 000B (11) │ │ │ │ +9F66F Version 01 (1) │ │ │ │ +9F670 UID Size 04 (4) │ │ │ │ +9F671 UID 00000000 (0) │ │ │ │ +9F675 GID Size 04 (4) │ │ │ │ +9F676 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F67A CENTRAL HEADER #12 02014B50 (33639248) │ │ │ │ +9F67E Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F67F Created OS 03 (3) 'Unix' │ │ │ │ +9F680 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F681 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F682 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F684 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F686 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F68A CRC 464C78A5 (1179416741) │ │ │ │ +9F68E Compressed Size 00003B2A (15146) │ │ │ │ +9F692 Uncompressed Size 0001B412 (111634) │ │ │ │ +9F696 Filename Length 0015 (21) │ │ │ │ +9F698 Extra Length 0018 (24) │ │ │ │ +9F69A Comment Length 0000 (0) │ │ │ │ +9F69C Disk Start 0000 (0) │ │ │ │ +9F69E Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F6A0 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F6A4 Local Header Offset 00016E02 (93698) │ │ │ │ +9F6A8 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F6A8: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F6BD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F6BF Length 0005 (5) │ │ │ │ +9F6C1 Flags 01 (1) 'Modification' │ │ │ │ +9F6C2 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F6C6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F6C8 Length 000B (11) │ │ │ │ +9F6CA Version 01 (1) │ │ │ │ +9F6CB UID Size 04 (4) │ │ │ │ +9F6CC UID 00000000 (0) │ │ │ │ +9F6D0 GID Size 04 (4) │ │ │ │ +9F6D1 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F6D5 CENTRAL HEADER #13 02014B50 (33639248) │ │ │ │ +9F6D9 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F6DA Created OS 03 (3) 'Unix' │ │ │ │ +9F6DB Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F6DC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F6DD General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F6DF Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F6E1 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F6E5 CRC C30A3E97 (3272228503) │ │ │ │ +9F6E9 Compressed Size 0000915B (37211) │ │ │ │ +9F6ED Uncompressed Size 0003D921 (252193) │ │ │ │ +9F6F1 Filename Length 0014 (20) │ │ │ │ +9F6F3 Extra Length 0018 (24) │ │ │ │ +9F6F5 Comment Length 0000 (0) │ │ │ │ +9F6F7 Disk Start 0000 (0) │ │ │ │ +9F6F9 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F6FB Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F6FF Local Header Offset 0001A97B (108923) │ │ │ │ +9F703 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F703: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F717 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F719 Length 0005 (5) │ │ │ │ +9F71B Flags 01 (1) 'Modification' │ │ │ │ +9F71C Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F720 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F722 Length 000B (11) │ │ │ │ +9F724 Version 01 (1) │ │ │ │ +9F725 UID Size 04 (4) │ │ │ │ +9F726 UID 00000000 (0) │ │ │ │ +9F72A GID Size 04 (4) │ │ │ │ +9F72B GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F72F CENTRAL HEADER #14 02014B50 (33639248) │ │ │ │ +9F733 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F734 Created OS 03 (3) 'Unix' │ │ │ │ +9F735 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F736 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F737 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F739 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F73B Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F73F CRC 8F9E34DE (2409510110) │ │ │ │ +9F743 Compressed Size 00002A76 (10870) │ │ │ │ +9F747 Uncompressed Size 00011520 (70944) │ │ │ │ +9F74B Filename Length 0016 (22) │ │ │ │ +9F74D Extra Length 0018 (24) │ │ │ │ +9F74F Comment Length 0000 (0) │ │ │ │ +9F751 Disk Start 0000 (0) │ │ │ │ +9F753 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F755 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F759 Local Header Offset 00023B24 (146212) │ │ │ │ +9F75D Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F75D: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F773 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F775 Length 0005 (5) │ │ │ │ +9F777 Flags 01 (1) 'Modification' │ │ │ │ +9F778 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F77C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F77E Length 000B (11) │ │ │ │ +9F780 Version 01 (1) │ │ │ │ +9F781 UID Size 04 (4) │ │ │ │ +9F782 UID 00000000 (0) │ │ │ │ +9F786 GID Size 04 (4) │ │ │ │ +9F787 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F78B CENTRAL HEADER #15 02014B50 (33639248) │ │ │ │ +9F78F Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F790 Created OS 03 (3) 'Unix' │ │ │ │ +9F791 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F792 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F793 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F795 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F797 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F79B CRC 1BE06878 (467691640) │ │ │ │ +9F79F Compressed Size 000014DC (5340) │ │ │ │ +9F7A3 Uncompressed Size 0000518E (20878) │ │ │ │ +9F7A7 Filename Length 001D (29) │ │ │ │ +9F7A9 Extra Length 0018 (24) │ │ │ │ +9F7AB Comment Length 0000 (0) │ │ │ │ +9F7AD Disk Start 0000 (0) │ │ │ │ +9F7AF Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F7B1 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F7B5 Local Header Offset 000265EA (157162) │ │ │ │ +9F7B9 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F7B9: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F7D6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F7D8 Length 0005 (5) │ │ │ │ +9F7DA Flags 01 (1) 'Modification' │ │ │ │ +9F7DB Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F7DF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F7E1 Length 000B (11) │ │ │ │ +9F7E3 Version 01 (1) │ │ │ │ +9F7E4 UID Size 04 (4) │ │ │ │ +9F7E5 UID 00000000 (0) │ │ │ │ +9F7E9 GID Size 04 (4) │ │ │ │ +9F7EA GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F7EE CENTRAL HEADER #16 02014B50 (33639248) │ │ │ │ +9F7F2 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F7F3 Created OS 03 (3) 'Unix' │ │ │ │ +9F7F4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F7F5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F7F6 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F7F8 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F7FA Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F7FE CRC CA50693D (3394267453) │ │ │ │ +9F802 Compressed Size 000039C8 (14792) │ │ │ │ +9F806 Uncompressed Size 0000F03B (61499) │ │ │ │ +9F80A Filename Length 001C (28) │ │ │ │ +9F80C Extra Length 0018 (24) │ │ │ │ +9F80E Comment Length 0000 (0) │ │ │ │ +9F810 Disk Start 0000 (0) │ │ │ │ +9F812 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F814 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F818 Local Header Offset 00027B1D (162589) │ │ │ │ +9F81C Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F81C: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F838 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F83A Length 0005 (5) │ │ │ │ +9F83C Flags 01 (1) 'Modification' │ │ │ │ +9F83D Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F841 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F843 Length 000B (11) │ │ │ │ +9F845 Version 01 (1) │ │ │ │ +9F846 UID Size 04 (4) │ │ │ │ +9F847 UID 00000000 (0) │ │ │ │ +9F84B GID Size 04 (4) │ │ │ │ +9F84C GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F850 CENTRAL HEADER #17 02014B50 (33639248) │ │ │ │ +9F854 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F855 Created OS 03 (3) 'Unix' │ │ │ │ +9F856 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F857 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F858 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F85A Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F85C Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F860 CRC 7A88ED58 (2055794008) │ │ │ │ +9F864 Compressed Size 000006A3 (1699) │ │ │ │ +9F868 Uncompressed Size 000011F5 (4597) │ │ │ │ +9F86C Filename Length 001C (28) │ │ │ │ +9F86E Extra Length 0018 (24) │ │ │ │ +9F870 Comment Length 0000 (0) │ │ │ │ +9F872 Disk Start 0000 (0) │ │ │ │ +9F874 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F876 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F87A Local Header Offset 0002B53B (177467) │ │ │ │ +9F87E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F87E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F89A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F89C Length 0005 (5) │ │ │ │ +9F89E Flags 01 (1) 'Modification' │ │ │ │ +9F89F Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F8A3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F8A5 Length 000B (11) │ │ │ │ +9F8A7 Version 01 (1) │ │ │ │ +9F8A8 UID Size 04 (4) │ │ │ │ +9F8A9 UID 00000000 (0) │ │ │ │ +9F8AD GID Size 04 (4) │ │ │ │ +9F8AE GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F8B2 CENTRAL HEADER #18 02014B50 (33639248) │ │ │ │ +9F8B6 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F8B7 Created OS 03 (3) 'Unix' │ │ │ │ +9F8B8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F8B9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F8BA General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F8BC Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F8BE Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F8C2 CRC D8039A55 (3624114773) │ │ │ │ +9F8C6 Compressed Size 0000107D (4221) │ │ │ │ +9F8CA Uncompressed Size 00004BE9 (19433) │ │ │ │ +9F8CE Filename Length 001B (27) │ │ │ │ +9F8D0 Extra Length 0018 (24) │ │ │ │ +9F8D2 Comment Length 0000 (0) │ │ │ │ +9F8D4 Disk Start 0000 (0) │ │ │ │ +9F8D6 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F8D8 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F8DC Local Header Offset 0002BC34 (179252) │ │ │ │ +9F8E0 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F8E0: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F8FB Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F8FD Length 0005 (5) │ │ │ │ +9F8FF Flags 01 (1) 'Modification' │ │ │ │ +9F900 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F904 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F906 Length 000B (11) │ │ │ │ +9F908 Version 01 (1) │ │ │ │ +9F909 UID Size 04 (4) │ │ │ │ +9F90A UID 00000000 (0) │ │ │ │ +9F90E GID Size 04 (4) │ │ │ │ +9F90F GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F913 CENTRAL HEADER #19 02014B50 (33639248) │ │ │ │ +9F917 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F918 Created OS 03 (3) 'Unix' │ │ │ │ +9F919 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F91A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F91B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F91D Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F91F Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F923 CRC 41197FD7 (1092190167) │ │ │ │ +9F927 Compressed Size 000033AC (13228) │ │ │ │ +9F92B Uncompressed Size 0000BC95 (48277) │ │ │ │ +9F92F Filename Length 001D (29) │ │ │ │ +9F931 Extra Length 0018 (24) │ │ │ │ +9F933 Comment Length 0000 (0) │ │ │ │ +9F935 Disk Start 0000 (0) │ │ │ │ +9F937 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F939 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F93D Local Header Offset 0002CD06 (183558) │ │ │ │ +9F941 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F941: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F95E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F960 Length 0005 (5) │ │ │ │ +9F962 Flags 01 (1) 'Modification' │ │ │ │ +9F963 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F967 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F969 Length 000B (11) │ │ │ │ +9F96B Version 01 (1) │ │ │ │ +9F96C UID Size 04 (4) │ │ │ │ +9F96D UID 00000000 (0) │ │ │ │ +9F971 GID Size 04 (4) │ │ │ │ +9F972 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F976 CENTRAL HEADER #20 02014B50 (33639248) │ │ │ │ +9F97A Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F97B Created OS 03 (3) 'Unix' │ │ │ │ +9F97C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F97D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F97E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F980 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F982 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F986 CRC 338EF5C5 (865007045) │ │ │ │ +9F98A Compressed Size 00000D6F (3439) │ │ │ │ +9F98E Uncompressed Size 0000388E (14478) │ │ │ │ +9F992 Filename Length 001D (29) │ │ │ │ +9F994 Extra Length 0018 (24) │ │ │ │ +9F996 Comment Length 0000 (0) │ │ │ │ +9F998 Disk Start 0000 (0) │ │ │ │ +9F99A Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F99C Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9F9A0 Local Header Offset 00030109 (196873) │ │ │ │ +9F9A4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9F9A4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9F9C1 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9F9C3 Length 0005 (5) │ │ │ │ +9F9C5 Flags 01 (1) 'Modification' │ │ │ │ +9F9C6 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F9CA Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9F9CC Length 000B (11) │ │ │ │ +9F9CE Version 01 (1) │ │ │ │ +9F9CF UID Size 04 (4) │ │ │ │ +9F9D0 UID 00000000 (0) │ │ │ │ +9F9D4 GID Size 04 (4) │ │ │ │ +9F9D5 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9F9D9 CENTRAL HEADER #21 02014B50 (33639248) │ │ │ │ +9F9DD Created Zip Spec 3D (61) '6.1' │ │ │ │ +9F9DE Created OS 03 (3) 'Unix' │ │ │ │ +9F9DF Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9F9E0 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9F9E1 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9F9E3 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9F9E5 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9F9E9 CRC 0AA066F0 (178284272) │ │ │ │ +9F9ED Compressed Size 00001C89 (7305) │ │ │ │ +9F9F1 Uncompressed Size 0000C144 (49476) │ │ │ │ +9F9F5 Filename Length 001A (26) │ │ │ │ +9F9F7 Extra Length 0018 (24) │ │ │ │ +9F9F9 Comment Length 0000 (0) │ │ │ │ +9F9FB Disk Start 0000 (0) │ │ │ │ +9F9FD Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9F9FF Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FA03 Local Header Offset 00030ECF (200399) │ │ │ │ +9FA07 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FA07: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FA21 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FA23 Length 0005 (5) │ │ │ │ +9FA25 Flags 01 (1) 'Modification' │ │ │ │ +9FA26 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FA2A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FA2C Length 000B (11) │ │ │ │ +9FA2E Version 01 (1) │ │ │ │ +9FA2F UID Size 04 (4) │ │ │ │ +9FA30 UID 00000000 (0) │ │ │ │ +9FA34 GID Size 04 (4) │ │ │ │ +9FA35 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FA39 CENTRAL HEADER #22 02014B50 (33639248) │ │ │ │ +9FA3D Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FA3E Created OS 03 (3) 'Unix' │ │ │ │ +9FA3F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FA40 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FA41 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FA43 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FA45 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FA49 CRC C41B95CB (3290142155) │ │ │ │ +9FA4D Compressed Size 000003A4 (932) │ │ │ │ +9FA51 Uncompressed Size 0000088F (2191) │ │ │ │ +9FA55 Filename Length 0012 (18) │ │ │ │ +9FA57 Extra Length 0018 (24) │ │ │ │ +9FA59 Comment Length 0000 (0) │ │ │ │ +9FA5B Disk Start 0000 (0) │ │ │ │ +9FA5D Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FA5F Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FA63 Local Header Offset 00032BAC (207788) │ │ │ │ +9FA67 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FA67: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FA79 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FA7B Length 0005 (5) │ │ │ │ +9FA7D Flags 01 (1) 'Modification' │ │ │ │ +9FA7E Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FA82 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FA84 Length 000B (11) │ │ │ │ +9FA86 Version 01 (1) │ │ │ │ +9FA87 UID Size 04 (4) │ │ │ │ +9FA88 UID 00000000 (0) │ │ │ │ +9FA8C GID Size 04 (4) │ │ │ │ +9FA8D GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FA91 CENTRAL HEADER #23 02014B50 (33639248) │ │ │ │ +9FA95 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FA96 Created OS 03 (3) 'Unix' │ │ │ │ +9FA97 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FA98 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FA99 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FA9B Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FA9D Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FAA1 CRC 40B68220 (1085702688) │ │ │ │ +9FAA5 Compressed Size 000001D4 (468) │ │ │ │ +9FAA9 Uncompressed Size 00000312 (786) │ │ │ │ +9FAAD Filename Length 0020 (32) │ │ │ │ +9FAAF Extra Length 0018 (24) │ │ │ │ +9FAB1 Comment Length 0000 (0) │ │ │ │ +9FAB3 Disk Start 0000 (0) │ │ │ │ +9FAB5 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FAB7 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FABB Local Header Offset 00032F9C (208796) │ │ │ │ +9FABF Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FABF: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FADF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FAE1 Length 0005 (5) │ │ │ │ +9FAE3 Flags 01 (1) 'Modification' │ │ │ │ +9FAE4 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FAE8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FAEA Length 000B (11) │ │ │ │ +9FAEC Version 01 (1) │ │ │ │ +9FAED UID Size 04 (4) │ │ │ │ +9FAEE UID 00000000 (0) │ │ │ │ +9FAF2 GID Size 04 (4) │ │ │ │ +9FAF3 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FAF7 CENTRAL HEADER #24 02014B50 (33639248) │ │ │ │ +9FAFB Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FAFC Created OS 03 (3) 'Unix' │ │ │ │ +9FAFD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FAFE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FAFF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FB01 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FB03 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FB07 CRC 404001E8 (1077936616) │ │ │ │ +9FB0B Compressed Size 000017AD (6061) │ │ │ │ +9FB0F Uncompressed Size 00009D19 (40217) │ │ │ │ +9FB13 Filename Length 001B (27) │ │ │ │ +9FB15 Extra Length 0018 (24) │ │ │ │ +9FB17 Comment Length 0000 (0) │ │ │ │ +9FB19 Disk Start 0000 (0) │ │ │ │ +9FB1B Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FB1D Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FB21 Local Header Offset 000331CA (209354) │ │ │ │ +9FB25 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FB25: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FB40 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FB42 Length 0005 (5) │ │ │ │ +9FB44 Flags 01 (1) 'Modification' │ │ │ │ +9FB45 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FB49 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FB4B Length 000B (11) │ │ │ │ +9FB4D Version 01 (1) │ │ │ │ +9FB4E UID Size 04 (4) │ │ │ │ +9FB4F UID 00000000 (0) │ │ │ │ +9FB53 GID Size 04 (4) │ │ │ │ +9FB54 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FB58 CENTRAL HEADER #25 02014B50 (33639248) │ │ │ │ +9FB5C Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FB5D Created OS 03 (3) 'Unix' │ │ │ │ +9FB5E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FB5F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FB60 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FB62 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FB64 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FB68 CRC 8B976C32 (2341956658) │ │ │ │ +9FB6C Compressed Size 00001374 (4980) │ │ │ │ +9FB70 Uncompressed Size 00003B67 (15207) │ │ │ │ +9FB74 Filename Length 0015 (21) │ │ │ │ +9FB76 Extra Length 0018 (24) │ │ │ │ +9FB78 Comment Length 0000 (0) │ │ │ │ +9FB7A Disk Start 0000 (0) │ │ │ │ +9FB7C Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FB7E Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FB82 Local Header Offset 000349CC (215500) │ │ │ │ +9FB86 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FB86: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FB9B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FB9D Length 0005 (5) │ │ │ │ +9FB9F Flags 01 (1) 'Modification' │ │ │ │ +9FBA0 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FBA4 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FBA6 Length 000B (11) │ │ │ │ +9FBA8 Version 01 (1) │ │ │ │ +9FBA9 UID Size 04 (4) │ │ │ │ +9FBAA UID 00000000 (0) │ │ │ │ +9FBAE GID Size 04 (4) │ │ │ │ +9FBAF GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FBB3 CENTRAL HEADER #26 02014B50 (33639248) │ │ │ │ +9FBB7 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FBB8 Created OS 03 (3) 'Unix' │ │ │ │ +9FBB9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FBBA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FBBB General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FBBD Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FBBF Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FBC3 CRC 2ACCA91C (718055708) │ │ │ │ +9FBC7 Compressed Size 00000AD3 (2771) │ │ │ │ +9FBCB Uncompressed Size 00002136 (8502) │ │ │ │ +9FBCF Filename Length 0011 (17) │ │ │ │ +9FBD1 Extra Length 0018 (24) │ │ │ │ +9FBD3 Comment Length 0000 (0) │ │ │ │ +9FBD5 Disk Start 0000 (0) │ │ │ │ +9FBD7 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FBD9 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FBDD Local Header Offset 00035D8F (220559) │ │ │ │ +9FBE1 Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FBE1: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FBF2 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FBF4 Length 0005 (5) │ │ │ │ +9FBF6 Flags 01 (1) 'Modification' │ │ │ │ +9FBF7 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FBFB Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FBFD Length 000B (11) │ │ │ │ +9FBFF Version 01 (1) │ │ │ │ +9FC00 UID Size 04 (4) │ │ │ │ +9FC01 UID 00000000 (0) │ │ │ │ +9FC05 GID Size 04 (4) │ │ │ │ +9FC06 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FC0A CENTRAL HEADER #27 02014B50 (33639248) │ │ │ │ +9FC0E Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FC0F Created OS 03 (3) 'Unix' │ │ │ │ +9FC10 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FC11 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FC12 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FC14 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FC16 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FC1A CRC E7227DBB (3877797307) │ │ │ │ +9FC1E Compressed Size 000003FE (1022) │ │ │ │ +9FC22 Uncompressed Size 00000F0D (3853) │ │ │ │ +9FC26 Filename Length 0014 (20) │ │ │ │ +9FC28 Extra Length 0018 (24) │ │ │ │ +9FC2A Comment Length 0000 (0) │ │ │ │ +9FC2C Disk Start 0000 (0) │ │ │ │ +9FC2E Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FC30 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FC34 Local Header Offset 000368AD (223405) │ │ │ │ +9FC38 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FC38: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FC4C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FC4E Length 0005 (5) │ │ │ │ +9FC50 Flags 01 (1) 'Modification' │ │ │ │ +9FC51 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FC55 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FC57 Length 000B (11) │ │ │ │ +9FC59 Version 01 (1) │ │ │ │ +9FC5A UID Size 04 (4) │ │ │ │ +9FC5B UID 00000000 (0) │ │ │ │ +9FC5F GID Size 04 (4) │ │ │ │ +9FC60 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FC64 CENTRAL HEADER #28 02014B50 (33639248) │ │ │ │ +9FC68 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FC69 Created OS 03 (3) 'Unix' │ │ │ │ +9FC6A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FC6B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FC6C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FC6E Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FC70 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FC74 CRC F2ED2F43 (4075630403) │ │ │ │ +9FC78 Compressed Size 00001263 (4707) │ │ │ │ +9FC7C Uncompressed Size 0000346A (13418) │ │ │ │ +9FC80 Filename Length 0014 (20) │ │ │ │ +9FC82 Extra Length 0018 (24) │ │ │ │ +9FC84 Comment Length 0000 (0) │ │ │ │ +9FC86 Disk Start 0000 (0) │ │ │ │ +9FC88 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FC8A Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FC8E Local Header Offset 00036CF9 (224505) │ │ │ │ +9FC92 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FC92: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FCA6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FCA8 Length 0005 (5) │ │ │ │ +9FCAA Flags 01 (1) 'Modification' │ │ │ │ +9FCAB Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FCAF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FCB1 Length 000B (11) │ │ │ │ +9FCB3 Version 01 (1) │ │ │ │ +9FCB4 UID Size 04 (4) │ │ │ │ +9FCB5 UID 00000000 (0) │ │ │ │ +9FCB9 GID Size 04 (4) │ │ │ │ +9FCBA GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FCBE CENTRAL HEADER #29 02014B50 (33639248) │ │ │ │ +9FCC2 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FCC3 Created OS 03 (3) 'Unix' │ │ │ │ +9FCC4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FCC5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FCC6 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FCC8 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FCCA Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FCCE CRC B778F93A (3078158650) │ │ │ │ +9FCD2 Compressed Size 00000AD0 (2768) │ │ │ │ +9FCD6 Uncompressed Size 00002300 (8960) │ │ │ │ +9FCDA Filename Length 001B (27) │ │ │ │ +9FCDC Extra Length 0018 (24) │ │ │ │ +9FCDE Comment Length 0000 (0) │ │ │ │ +9FCE0 Disk Start 0000 (0) │ │ │ │ +9FCE2 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FCE4 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FCE8 Local Header Offset 00037FAA (229290) │ │ │ │ +9FCEC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FCEC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FD07 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FD09 Length 0005 (5) │ │ │ │ +9FD0B Flags 01 (1) 'Modification' │ │ │ │ +9FD0C Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FD10 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FD12 Length 000B (11) │ │ │ │ +9FD14 Version 01 (1) │ │ │ │ +9FD15 UID Size 04 (4) │ │ │ │ +9FD16 UID 00000000 (0) │ │ │ │ +9FD1A GID Size 04 (4) │ │ │ │ +9FD1B GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FD1F CENTRAL HEADER #30 02014B50 (33639248) │ │ │ │ +9FD23 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FD24 Created OS 03 (3) 'Unix' │ │ │ │ +9FD25 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FD26 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FD27 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FD29 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FD2B Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FD2F CRC 48CED347 (1221514055) │ │ │ │ +9FD33 Compressed Size 00000A94 (2708) │ │ │ │ +9FD37 Uncompressed Size 00002365 (9061) │ │ │ │ +9FD3B Filename Length 0013 (19) │ │ │ │ +9FD3D Extra Length 0018 (24) │ │ │ │ +9FD3F Comment Length 0000 (0) │ │ │ │ +9FD41 Disk Start 0000 (0) │ │ │ │ +9FD43 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FD45 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FD49 Local Header Offset 00038ACF (232143) │ │ │ │ +9FD4D Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FD4D: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FD60 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FD62 Length 0005 (5) │ │ │ │ +9FD64 Flags 01 (1) 'Modification' │ │ │ │ +9FD65 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FD69 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FD6B Length 000B (11) │ │ │ │ +9FD6D Version 01 (1) │ │ │ │ +9FD6E UID Size 04 (4) │ │ │ │ +9FD6F UID 00000000 (0) │ │ │ │ +9FD73 GID Size 04 (4) │ │ │ │ +9FD74 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FD78 CENTRAL HEADER #31 02014B50 (33639248) │ │ │ │ +9FD7C Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FD7D Created OS 03 (3) 'Unix' │ │ │ │ +9FD7E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FD7F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FD80 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FD82 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FD84 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FD88 CRC 2FCB6E58 (801861208) │ │ │ │ +9FD8C Compressed Size 000010D9 (4313) │ │ │ │ +9FD90 Uncompressed Size 000055E3 (21987) │ │ │ │ +9FD94 Filename Length 000F (15) │ │ │ │ +9FD96 Extra Length 0018 (24) │ │ │ │ +9FD98 Comment Length 0000 (0) │ │ │ │ +9FD9A Disk Start 0000 (0) │ │ │ │ +9FD9C Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FD9E Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FDA2 Local Header Offset 000395B0 (234928) │ │ │ │ +9FDA6 Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FDA6: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FDB5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FDB7 Length 0005 (5) │ │ │ │ +9FDB9 Flags 01 (1) 'Modification' │ │ │ │ +9FDBA Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FDBE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FDC0 Length 000B (11) │ │ │ │ +9FDC2 Version 01 (1) │ │ │ │ +9FDC3 UID Size 04 (4) │ │ │ │ +9FDC4 UID 00000000 (0) │ │ │ │ +9FDC8 GID Size 04 (4) │ │ │ │ +9FDC9 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FDCD CENTRAL HEADER #32 02014B50 (33639248) │ │ │ │ +9FDD1 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FDD2 Created OS 03 (3) 'Unix' │ │ │ │ +9FDD3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FDD4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FDD5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FDD7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FDD9 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FDDD CRC C70CE575 (3339511157) │ │ │ │ +9FDE1 Compressed Size 0000066B (1643) │ │ │ │ +9FDE5 Uncompressed Size 000018E0 (6368) │ │ │ │ +9FDE9 Filename Length 000F (15) │ │ │ │ +9FDEB Extra Length 0018 (24) │ │ │ │ +9FDED Comment Length 0000 (0) │ │ │ │ +9FDEF Disk Start 0000 (0) │ │ │ │ +9FDF1 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FDF3 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FDF7 Local Header Offset 0003A6D2 (239314) │ │ │ │ +9FDFB Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FDFB: Filename 'XXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FE0A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FE0C Length 0005 (5) │ │ │ │ +9FE0E Flags 01 (1) 'Modification' │ │ │ │ +9FE0F Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FE13 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FE15 Length 000B (11) │ │ │ │ +9FE17 Version 01 (1) │ │ │ │ +9FE18 UID Size 04 (4) │ │ │ │ +9FE19 UID 00000000 (0) │ │ │ │ +9FE1D GID Size 04 (4) │ │ │ │ +9FE1E GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FE22 CENTRAL HEADER #33 02014B50 (33639248) │ │ │ │ +9FE26 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FE27 Created OS 03 (3) 'Unix' │ │ │ │ +9FE28 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FE29 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FE2A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FE2C Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FE2E Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FE32 CRC CEB50239 (3467969081) │ │ │ │ +9FE36 Compressed Size 00001A61 (6753) │ │ │ │ +9FE3A Uncompressed Size 000065AF (26031) │ │ │ │ +9FE3E Filename Length 0013 (19) │ │ │ │ +9FE40 Extra Length 0018 (24) │ │ │ │ +9FE42 Comment Length 0000 (0) │ │ │ │ +9FE44 Disk Start 0000 (0) │ │ │ │ +9FE46 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FE48 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FE4C Local Header Offset 0003AD86 (241030) │ │ │ │ +9FE50 Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FE50: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FE63 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FE65 Length 0005 (5) │ │ │ │ +9FE67 Flags 01 (1) 'Modification' │ │ │ │ +9FE68 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FE6C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FE6E Length 000B (11) │ │ │ │ +9FE70 Version 01 (1) │ │ │ │ +9FE71 UID Size 04 (4) │ │ │ │ +9FE72 UID 00000000 (0) │ │ │ │ +9FE76 GID Size 04 (4) │ │ │ │ +9FE77 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FE7B CENTRAL HEADER #34 02014B50 (33639248) │ │ │ │ +9FE7F Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FE80 Created OS 03 (3) 'Unix' │ │ │ │ +9FE81 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FE82 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FE83 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FE85 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FE87 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FE8B CRC BA8C2020 (3129745440) │ │ │ │ +9FE8F Compressed Size 000009A7 (2471) │ │ │ │ +9FE93 Uncompressed Size 00001B65 (7013) │ │ │ │ +9FE97 Filename Length 0010 (16) │ │ │ │ +9FE99 Extra Length 0018 (24) │ │ │ │ +9FE9B Comment Length 0000 (0) │ │ │ │ +9FE9D Disk Start 0000 (0) │ │ │ │ +9FE9F Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FEA1 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FEA5 Local Header Offset 0003C834 (247860) │ │ │ │ +9FEA9 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FEA9: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FEB9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FEBB Length 0005 (5) │ │ │ │ +9FEBD Flags 01 (1) 'Modification' │ │ │ │ +9FEBE Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FEC2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FEC4 Length 000B (11) │ │ │ │ +9FEC6 Version 01 (1) │ │ │ │ +9FEC7 UID Size 04 (4) │ │ │ │ +9FEC8 UID 00000000 (0) │ │ │ │ +9FECC GID Size 04 (4) │ │ │ │ +9FECD GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FED1 CENTRAL HEADER #35 02014B50 (33639248) │ │ │ │ +9FED5 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FED6 Created OS 03 (3) 'Unix' │ │ │ │ +9FED7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FED8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FED9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FEDB Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FEDD Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FEE1 CRC 61869F50 (1636212560) │ │ │ │ +9FEE5 Compressed Size 000006B9 (1721) │ │ │ │ +9FEE9 Uncompressed Size 00001566 (5478) │ │ │ │ +9FEED Filename Length 0012 (18) │ │ │ │ +9FEEF Extra Length 0018 (24) │ │ │ │ +9FEF1 Comment Length 0000 (0) │ │ │ │ +9FEF3 Disk Start 0000 (0) │ │ │ │ +9FEF5 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FEF7 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FEFB Local Header Offset 0003D225 (250405) │ │ │ │ +9FEFF Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FEFF: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FF11 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FF13 Length 0005 (5) │ │ │ │ +9FF15 Flags 01 (1) 'Modification' │ │ │ │ +9FF16 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 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 #36 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 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FF39 CRC 42F0CC6A (1123077226) │ │ │ │ +9FF3D Compressed Size 00002A18 (10776) │ │ │ │ +9FF41 Uncompressed Size 0000B1DD (45533) │ │ │ │ +9FF45 Filename Length 0010 (16) │ │ │ │ +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 0003D92A (252202) │ │ │ │ +9FF57 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FF57: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FF67 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FF69 Length 0005 (5) │ │ │ │ +9FF6B Flags 01 (1) 'Modification' │ │ │ │ +9FF6C Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FF70 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FF72 Length 000B (11) │ │ │ │ +9FF74 Version 01 (1) │ │ │ │ +9FF75 UID Size 04 (4) │ │ │ │ +9FF76 UID 00000000 (0) │ │ │ │ +9FF7A GID Size 04 (4) │ │ │ │ +9FF7B GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FF7F CENTRAL HEADER #37 02014B50 (33639248) │ │ │ │ +9FF83 Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FF84 Created OS 03 (3) 'Unix' │ │ │ │ +9FF85 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FF86 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FF87 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FF89 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FF8B Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FF8F CRC 25DE9EF7 (635346679) │ │ │ │ +9FF93 Compressed Size 00001E8C (7820) │ │ │ │ +9FF97 Uncompressed Size 00009940 (39232) │ │ │ │ +9FF9B Filename Length 0012 (18) │ │ │ │ +9FF9D Extra Length 0018 (24) │ │ │ │ +9FF9F Comment Length 0000 (0) │ │ │ │ +9FFA1 Disk Start 0000 (0) │ │ │ │ +9FFA3 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FFA5 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +9FFA9 Local Header Offset 0004038C (263052) │ │ │ │ +9FFAD Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0x9FFAD: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +9FFBF Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +9FFC1 Length 0005 (5) │ │ │ │ +9FFC3 Flags 01 (1) 'Modification' │ │ │ │ +9FFC4 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FFC8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +9FFCA Length 000B (11) │ │ │ │ +9FFCC Version 01 (1) │ │ │ │ +9FFCD UID Size 04 (4) │ │ │ │ +9FFCE UID 00000000 (0) │ │ │ │ +9FFD2 GID Size 04 (4) │ │ │ │ +9FFD3 GID 00000000 (0) │ │ │ │ + │ │ │ │ +9FFD7 CENTRAL HEADER #38 02014B50 (33639248) │ │ │ │ +9FFDB Created Zip Spec 3D (61) '6.1' │ │ │ │ +9FFDC Created OS 03 (3) 'Unix' │ │ │ │ +9FFDD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +9FFDE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +9FFDF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +9FFE1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +9FFE3 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +9FFE7 CRC 0F5462F7 (257188599) │ │ │ │ +9FFEB Compressed Size 0000144E (5198) │ │ │ │ +9FFEF Uncompressed Size 00007A46 (31302) │ │ │ │ +9FFF3 Filename Length 0018 (24) │ │ │ │ +9FFF5 Extra Length 0018 (24) │ │ │ │ +9FFF7 Comment Length 0000 (0) │ │ │ │ +9FFF9 Disk Start 0000 (0) │ │ │ │ +9FFFB Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +9FFFD Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0001 Local Header Offset 00042264 (270948) │ │ │ │ +A0005 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0005: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A001D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A001F Length 0005 (5) │ │ │ │ +A0021 Flags 01 (1) 'Modification' │ │ │ │ +A0022 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0026 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0028 Length 000B (11) │ │ │ │ +A002A Version 01 (1) │ │ │ │ +A002B UID Size 04 (4) │ │ │ │ +A002C UID 00000000 (0) │ │ │ │ +A0030 GID Size 04 (4) │ │ │ │ +A0031 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0035 CENTRAL HEADER #39 02014B50 (33639248) │ │ │ │ +A0039 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A003A Created OS 03 (3) 'Unix' │ │ │ │ +A003B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A003C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A003D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A003F Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0041 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0045 CRC 4D59ECAE (1297738926) │ │ │ │ +A0049 Compressed Size 000018DB (6363) │ │ │ │ +A004D Uncompressed Size 0000A83A (43066) │ │ │ │ +A0051 Filename Length 001F (31) │ │ │ │ +A0053 Extra Length 0018 (24) │ │ │ │ +A0055 Comment Length 0000 (0) │ │ │ │ +A0057 Disk Start 0000 (0) │ │ │ │ +A0059 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A005B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A005F Local Header Offset 00043704 (276228) │ │ │ │ +A0063 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0063: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0082 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0084 Length 0005 (5) │ │ │ │ +A0086 Flags 01 (1) 'Modification' │ │ │ │ +A0087 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A008B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A008D Length 000B (11) │ │ │ │ +A008F Version 01 (1) │ │ │ │ +A0090 UID Size 04 (4) │ │ │ │ +A0091 UID 00000000 (0) │ │ │ │ +A0095 GID Size 04 (4) │ │ │ │ +A0096 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A009A CENTRAL HEADER #40 02014B50 (33639248) │ │ │ │ +A009E Created Zip Spec 3D (61) '6.1' │ │ │ │ +A009F Created OS 03 (3) 'Unix' │ │ │ │ +A00A0 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A00A1 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A00A2 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A00A4 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A00A6 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A00AA CRC 8E4EBF9E (2387525534) │ │ │ │ +A00AE Compressed Size 000003F8 (1016) │ │ │ │ +A00B2 Uncompressed Size 000008A4 (2212) │ │ │ │ +A00B6 Filename Length 001E (30) │ │ │ │ +A00B8 Extra Length 0018 (24) │ │ │ │ +A00BA Comment Length 0000 (0) │ │ │ │ +A00BC Disk Start 0000 (0) │ │ │ │ +A00BE Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A00C0 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A00C4 Local Header Offset 00045038 (282680) │ │ │ │ +A00C8 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA00C8: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A00E6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A00E8 Length 0005 (5) │ │ │ │ +A00EA Flags 01 (1) 'Modification' │ │ │ │ +A00EB Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A00EF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A00F1 Length 000B (11) │ │ │ │ +A00F3 Version 01 (1) │ │ │ │ +A00F4 UID Size 04 (4) │ │ │ │ +A00F5 UID 00000000 (0) │ │ │ │ +A00F9 GID Size 04 (4) │ │ │ │ +A00FA GID 00000000 (0) │ │ │ │ + │ │ │ │ +A00FE CENTRAL HEADER #41 02014B50 (33639248) │ │ │ │ +A0102 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0103 Created OS 03 (3) 'Unix' │ │ │ │ +A0104 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0105 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0106 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0108 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A010A Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A010E CRC D9FD2FF3 (3657248755) │ │ │ │ +A0112 Compressed Size 00004296 (17046) │ │ │ │ +A0116 Uncompressed Size 0000D8E8 (55528) │ │ │ │ +A011A Filename Length 0013 (19) │ │ │ │ +A011C Extra Length 0018 (24) │ │ │ │ +A011E Comment Length 0000 (0) │ │ │ │ +A0120 Disk Start 0000 (0) │ │ │ │ +A0122 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0124 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0128 Local Header Offset 00045488 (283784) │ │ │ │ +A012C Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA012C: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A013F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0141 Length 0005 (5) │ │ │ │ +A0143 Flags 01 (1) 'Modification' │ │ │ │ +A0144 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0148 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A014A Length 000B (11) │ │ │ │ +A014C Version 01 (1) │ │ │ │ +A014D UID Size 04 (4) │ │ │ │ +A014E UID 00000000 (0) │ │ │ │ +A0152 GID Size 04 (4) │ │ │ │ +A0153 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0157 CENTRAL HEADER #42 02014B50 (33639248) │ │ │ │ +A015B Created Zip Spec 3D (61) '6.1' │ │ │ │ +A015C Created OS 03 (3) 'Unix' │ │ │ │ +A015D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A015E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A015F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0161 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0163 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0167 CRC F2AE95AF (4071527855) │ │ │ │ +A016B Compressed Size 000026C5 (9925) │ │ │ │ +A016F Uncompressed Size 00006E46 (28230) │ │ │ │ +A0173 Filename Length 0019 (25) │ │ │ │ +A0175 Extra Length 0018 (24) │ │ │ │ +A0177 Comment Length 0000 (0) │ │ │ │ +A0179 Disk Start 0000 (0) │ │ │ │ +A017B Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A017D Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0181 Local Header Offset 0004976B (300907) │ │ │ │ +A0185 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0185: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A019E Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A01A0 Length 0005 (5) │ │ │ │ +A01A2 Flags 01 (1) 'Modification' │ │ │ │ +A01A3 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A01A7 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A01A9 Length 000B (11) │ │ │ │ +A01AB Version 01 (1) │ │ │ │ +A01AC UID Size 04 (4) │ │ │ │ +A01AD UID 00000000 (0) │ │ │ │ +A01B1 GID Size 04 (4) │ │ │ │ +A01B2 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A01B6 CENTRAL HEADER #43 02014B50 (33639248) │ │ │ │ +A01BA Created Zip Spec 3D (61) '6.1' │ │ │ │ +A01BB Created OS 03 (3) 'Unix' │ │ │ │ +A01BC Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A01BD Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A01BE General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A01C0 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A01C2 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A01C6 CRC A84F7DF4 (2823781876) │ │ │ │ +A01CA Compressed Size 0000273B (10043) │ │ │ │ +A01CE Uncompressed Size 00008B84 (35716) │ │ │ │ +A01D2 Filename Length 0019 (25) │ │ │ │ +A01D4 Extra Length 0018 (24) │ │ │ │ +A01D6 Comment Length 0000 (0) │ │ │ │ +A01D8 Disk Start 0000 (0) │ │ │ │ +A01DA Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A01DC Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A01E0 Local Header Offset 0004BE83 (310915) │ │ │ │ +A01E4 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA01E4: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A01FD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A01FF Length 0005 (5) │ │ │ │ +A0201 Flags 01 (1) 'Modification' │ │ │ │ +A0202 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0206 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0208 Length 000B (11) │ │ │ │ +A020A Version 01 (1) │ │ │ │ +A020B UID Size 04 (4) │ │ │ │ +A020C UID 00000000 (0) │ │ │ │ +A0210 GID Size 04 (4) │ │ │ │ +A0211 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0215 CENTRAL HEADER #44 02014B50 (33639248) │ │ │ │ +A0219 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A021A Created OS 03 (3) 'Unix' │ │ │ │ +A021B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A021C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A021D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A021F Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0221 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0225 CRC 5568CA0F (1432930831) │ │ │ │ +A0229 Compressed Size 00000CF8 (3320) │ │ │ │ +A022D Uncompressed Size 0000517B (20859) │ │ │ │ +A0231 Filename Length 0021 (33) │ │ │ │ +A0233 Extra Length 0018 (24) │ │ │ │ +A0235 Comment Length 0000 (0) │ │ │ │ +A0237 Disk Start 0000 (0) │ │ │ │ +A0239 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A023B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A023F Local Header Offset 0004E611 (321041) │ │ │ │ +A0243 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0243: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0264 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0266 Length 0005 (5) │ │ │ │ +A0268 Flags 01 (1) 'Modification' │ │ │ │ +A0269 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A026D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A026F Length 000B (11) │ │ │ │ +A0271 Version 01 (1) │ │ │ │ +A0272 UID Size 04 (4) │ │ │ │ +A0273 UID 00000000 (0) │ │ │ │ +A0277 GID Size 04 (4) │ │ │ │ +A0278 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A027C CENTRAL HEADER #45 02014B50 (33639248) │ │ │ │ +A0280 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0281 Created OS 03 (3) 'Unix' │ │ │ │ +A0282 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0283 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0284 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0286 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0288 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A028C CRC 4527F9F0 (1160247792) │ │ │ │ +A0290 Compressed Size 00000469 (1129) │ │ │ │ +A0294 Uncompressed Size 00000932 (2354) │ │ │ │ +A0298 Filename Length 001B (27) │ │ │ │ +A029A Extra Length 0018 (24) │ │ │ │ +A029C Comment Length 0000 (0) │ │ │ │ +A029E Disk Start 0000 (0) │ │ │ │ +A02A0 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A02A2 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A02A6 Local Header Offset 0004F364 (324452) │ │ │ │ +A02AA Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA02AA: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A02C5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A02C7 Length 0005 (5) │ │ │ │ +A02C9 Flags 01 (1) 'Modification' │ │ │ │ +A02CA Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A02CE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A02D0 Length 000B (11) │ │ │ │ +A02D2 Version 01 (1) │ │ │ │ +A02D3 UID Size 04 (4) │ │ │ │ +A02D4 UID 00000000 (0) │ │ │ │ +A02D8 GID Size 04 (4) │ │ │ │ +A02D9 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A02DD CENTRAL HEADER #46 02014B50 (33639248) │ │ │ │ +A02E1 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A02E2 Created OS 03 (3) 'Unix' │ │ │ │ +A02E3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A02E4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A02E5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A02E7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A02E9 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A02ED CRC BEA0644D (3198182477) │ │ │ │ +A02F1 Compressed Size 00001704 (5892) │ │ │ │ +A02F5 Uncompressed Size 00007A12 (31250) │ │ │ │ +A02F9 Filename Length 001F (31) │ │ │ │ +A02FB Extra Length 0018 (24) │ │ │ │ +A02FD Comment Length 0000 (0) │ │ │ │ +A02FF Disk Start 0000 (0) │ │ │ │ +A0301 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0303 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0307 Local Header Offset 0004F822 (325666) │ │ │ │ +A030B Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA030B: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A032A Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A032C Length 0005 (5) │ │ │ │ +A032E Flags 01 (1) 'Modification' │ │ │ │ +A032F Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0333 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0335 Length 000B (11) │ │ │ │ +A0337 Version 01 (1) │ │ │ │ +A0338 UID Size 04 (4) │ │ │ │ +A0339 UID 00000000 (0) │ │ │ │ +A033D GID Size 04 (4) │ │ │ │ +A033E GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0342 CENTRAL HEADER #47 02014B50 (33639248) │ │ │ │ +A0346 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0347 Created OS 03 (3) 'Unix' │ │ │ │ +A0348 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0349 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A034A General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A034C Compression Method 0008 (8) 'Deflated' │ │ │ │ +A034E Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0352 CRC DF4EAE4A (3746475594) │ │ │ │ +A0356 Compressed Size 00004162 (16738) │ │ │ │ +A035A Uncompressed Size 0001D147 (119111) │ │ │ │ +A035E Filename Length 0010 (16) │ │ │ │ +A0360 Extra Length 0018 (24) │ │ │ │ +A0362 Comment Length 0000 (0) │ │ │ │ +A0364 Disk Start 0000 (0) │ │ │ │ +A0366 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0368 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A036C Local Header Offset 00050F7F (331647) │ │ │ │ +A0370 Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0370: Filename 'XXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0380 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0382 Length 0005 (5) │ │ │ │ +A0384 Flags 01 (1) 'Modification' │ │ │ │ +A0385 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0389 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A038B Length 000B (11) │ │ │ │ +A038D Version 01 (1) │ │ │ │ +A038E UID Size 04 (4) │ │ │ │ +A038F UID 00000000 (0) │ │ │ │ +A0393 GID Size 04 (4) │ │ │ │ +A0394 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0398 CENTRAL HEADER #48 02014B50 (33639248) │ │ │ │ +A039C Created Zip Spec 3D (61) '6.1' │ │ │ │ +A039D Created OS 03 (3) 'Unix' │ │ │ │ +A039E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A039F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A03A0 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A03A2 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A03A4 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A03A8 CRC 1C486A07 (474507783) │ │ │ │ +A03AC Compressed Size 00000A98 (2712) │ │ │ │ +A03B0 Uncompressed Size 00002106 (8454) │ │ │ │ +A03B4 Filename Length 0014 (20) │ │ │ │ +A03B6 Extra Length 0018 (24) │ │ │ │ +A03B8 Comment Length 0000 (0) │ │ │ │ +A03BA Disk Start 0000 (0) │ │ │ │ +A03BC Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A03BE Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A03C2 Local Header Offset 0005512B (348459) │ │ │ │ +A03C6 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA03C6: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A03DA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A03DC Length 0005 (5) │ │ │ │ +A03DE Flags 01 (1) 'Modification' │ │ │ │ +A03DF Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A03E3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A03E5 Length 000B (11) │ │ │ │ +A03E7 Version 01 (1) │ │ │ │ +A03E8 UID Size 04 (4) │ │ │ │ +A03E9 UID 00000000 (0) │ │ │ │ +A03ED GID Size 04 (4) │ │ │ │ +A03EE GID 00000000 (0) │ │ │ │ + │ │ │ │ +A03F2 CENTRAL HEADER #49 02014B50 (33639248) │ │ │ │ +A03F6 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A03F7 Created OS 03 (3) 'Unix' │ │ │ │ +A03F8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A03F9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A03FA General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A03FC Compression Method 0008 (8) 'Deflated' │ │ │ │ +A03FE Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0402 CRC 4A9BE1BB (1251729851) │ │ │ │ +A0406 Compressed Size 0000AC10 (44048) │ │ │ │ +A040A Uncompressed Size 0003E93F (256319) │ │ │ │ +A040E Filename Length 0017 (23) │ │ │ │ +A0410 Extra Length 0018 (24) │ │ │ │ +A0412 Comment Length 0000 (0) │ │ │ │ +A0414 Disk Start 0000 (0) │ │ │ │ +A0416 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0418 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A041C Local Header Offset 00055C11 (351249) │ │ │ │ +A0420 Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0420: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0437 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0439 Length 0005 (5) │ │ │ │ +A043B Flags 01 (1) 'Modification' │ │ │ │ +A043C Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0440 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0442 Length 000B (11) │ │ │ │ +A0444 Version 01 (1) │ │ │ │ +A0445 UID Size 04 (4) │ │ │ │ +A0446 UID 00000000 (0) │ │ │ │ +A044A GID Size 04 (4) │ │ │ │ +A044B GID 00000000 (0) │ │ │ │ + │ │ │ │ +A044F CENTRAL HEADER #50 02014B50 (33639248) │ │ │ │ +A0453 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0454 Created OS 03 (3) 'Unix' │ │ │ │ +A0455 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0456 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0457 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0459 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A045B Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A045F CRC 34458E6B (876973675) │ │ │ │ +A0463 Compressed Size 00000461 (1121) │ │ │ │ +A0467 Uncompressed Size 00000DF4 (3572) │ │ │ │ +A046B Filename Length 0013 (19) │ │ │ │ +A046D Extra Length 0018 (24) │ │ │ │ +A046F Comment Length 0000 (0) │ │ │ │ +A0471 Disk Start 0000 (0) │ │ │ │ +A0473 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0475 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0479 Local Header Offset 00060872 (395378) │ │ │ │ +A047D Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA047D: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0490 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0492 Length 0005 (5) │ │ │ │ +A0494 Flags 01 (1) 'Modification' │ │ │ │ +A0495 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0499 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A049B Length 000B (11) │ │ │ │ +A049D Version 01 (1) │ │ │ │ +A049E UID Size 04 (4) │ │ │ │ +A049F UID 00000000 (0) │ │ │ │ +A04A3 GID Size 04 (4) │ │ │ │ +A04A4 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A04A8 CENTRAL HEADER #51 02014B50 (33639248) │ │ │ │ +A04AC Created Zip Spec 3D (61) '6.1' │ │ │ │ +A04AD Created OS 03 (3) 'Unix' │ │ │ │ +A04AE Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A04AF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A04B0 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A04B2 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A04B4 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A04B8 CRC 0F8B3149 (260780361) │ │ │ │ +A04BC Compressed Size 000014B8 (5304) │ │ │ │ +A04C0 Uncompressed Size 000067DB (26587) │ │ │ │ +A04C4 Filename Length 0012 (18) │ │ │ │ +A04C6 Extra Length 0018 (24) │ │ │ │ +A04C8 Comment Length 0000 (0) │ │ │ │ +A04CA Disk Start 0000 (0) │ │ │ │ +A04CC Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A04CE Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A04D2 Local Header Offset 00060D20 (396576) │ │ │ │ +A04D6 Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA04D6: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A04E8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A04EA Length 0005 (5) │ │ │ │ +A04EC Flags 01 (1) 'Modification' │ │ │ │ +A04ED Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A04F1 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A04F3 Length 000B (11) │ │ │ │ +A04F5 Version 01 (1) │ │ │ │ +A04F6 UID Size 04 (4) │ │ │ │ +A04F7 UID 00000000 (0) │ │ │ │ +A04FB GID Size 04 (4) │ │ │ │ +A04FC GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0500 CENTRAL HEADER #52 02014B50 (33639248) │ │ │ │ +A0504 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0505 Created OS 03 (3) 'Unix' │ │ │ │ +A0506 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0507 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0508 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A050A Compression Method 0008 (8) 'Deflated' │ │ │ │ +A050C Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0510 CRC 6392F351 (1670574929) │ │ │ │ +A0514 Compressed Size 000011F1 (4593) │ │ │ │ +A0518 Uncompressed Size 0000410D (16653) │ │ │ │ +A051C Filename Length 0012 (18) │ │ │ │ +A051E Extra Length 0018 (24) │ │ │ │ +A0520 Comment Length 0000 (0) │ │ │ │ +A0522 Disk Start 0000 (0) │ │ │ │ +A0524 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0526 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A052A Local Header Offset 00062224 (401956) │ │ │ │ +A052E Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA052E: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0540 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0542 Length 0005 (5) │ │ │ │ +A0544 Flags 01 (1) 'Modification' │ │ │ │ +A0545 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0549 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A054B Length 000B (11) │ │ │ │ +A054D Version 01 (1) │ │ │ │ +A054E UID Size 04 (4) │ │ │ │ +A054F UID 00000000 (0) │ │ │ │ +A0553 GID Size 04 (4) │ │ │ │ +A0554 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0558 CENTRAL HEADER #53 02014B50 (33639248) │ │ │ │ +A055C Created Zip Spec 3D (61) '6.1' │ │ │ │ +A055D Created OS 03 (3) 'Unix' │ │ │ │ +A055E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A055F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0560 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0562 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0564 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0568 CRC 07B7BFDA (129482714) │ │ │ │ +A056C Compressed Size 000009DA (2522) │ │ │ │ +A0570 Uncompressed Size 0000352A (13610) │ │ │ │ +A0574 Filename Length 0019 (25) │ │ │ │ +A0576 Extra Length 0018 (24) │ │ │ │ +A0578 Comment Length 0000 (0) │ │ │ │ +A057A Disk Start 0000 (0) │ │ │ │ +A057C Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A057E Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0582 Local Header Offset 00063461 (406625) │ │ │ │ +A0586 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0586: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A059F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A05A1 Length 0005 (5) │ │ │ │ +A05A3 Flags 01 (1) 'Modification' │ │ │ │ +A05A4 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A05A8 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A05AA Length 000B (11) │ │ │ │ +A05AC Version 01 (1) │ │ │ │ +A05AD UID Size 04 (4) │ │ │ │ +A05AE UID 00000000 (0) │ │ │ │ +A05B2 GID Size 04 (4) │ │ │ │ +A05B3 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A05B7 CENTRAL HEADER #54 02014B50 (33639248) │ │ │ │ +A05BB Created Zip Spec 3D (61) '6.1' │ │ │ │ +A05BC Created OS 03 (3) 'Unix' │ │ │ │ +A05BD Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A05BE Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A05BF General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A05C1 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A05C3 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A05C7 CRC 99B7902F (2578944047) │ │ │ │ +A05CB Compressed Size 0000203A (8250) │ │ │ │ +A05CF Uncompressed Size 00010919 (67865) │ │ │ │ +A05D3 Filename Length 0019 (25) │ │ │ │ +A05D5 Extra Length 0018 (24) │ │ │ │ +A05D7 Comment Length 0000 (0) │ │ │ │ +A05D9 Disk Start 0000 (0) │ │ │ │ +A05DB Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A05DD Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A05E1 Local Header Offset 00063E8E (409230) │ │ │ │ +A05E5 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA05E5: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A05FE Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0600 Length 0005 (5) │ │ │ │ +A0602 Flags 01 (1) 'Modification' │ │ │ │ +A0603 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0607 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0609 Length 000B (11) │ │ │ │ +A060B Version 01 (1) │ │ │ │ +A060C UID Size 04 (4) │ │ │ │ +A060D UID 00000000 (0) │ │ │ │ +A0611 GID Size 04 (4) │ │ │ │ +A0612 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0616 CENTRAL HEADER #55 02014B50 (33639248) │ │ │ │ +A061A Created Zip Spec 3D (61) '6.1' │ │ │ │ +A061B Created OS 03 (3) 'Unix' │ │ │ │ +A061C Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A061D Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A061E General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0620 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0622 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0626 CRC D0372DB1 (3493277105) │ │ │ │ +A062A Compressed Size 0000177F (6015) │ │ │ │ +A062E Uncompressed Size 0000472D (18221) │ │ │ │ +A0632 Filename Length 0014 (20) │ │ │ │ +A0634 Extra Length 0018 (24) │ │ │ │ +A0636 Comment Length 0000 (0) │ │ │ │ +A0638 Disk Start 0000 (0) │ │ │ │ +A063A Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A063C Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0640 Local Header Offset 00065F1B (417563) │ │ │ │ +A0644 Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0644: Filename 'XXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0658 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A065A Length 0005 (5) │ │ │ │ +A065C Flags 01 (1) 'Modification' │ │ │ │ +A065D Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0661 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0663 Length 000B (11) │ │ │ │ +A0665 Version 01 (1) │ │ │ │ +A0666 UID Size 04 (4) │ │ │ │ +A0667 UID 00000000 (0) │ │ │ │ +A066B GID Size 04 (4) │ │ │ │ +A066C GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0670 CENTRAL HEADER #56 02014B50 (33639248) │ │ │ │ +A0674 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0675 Created OS 03 (3) 'Unix' │ │ │ │ +A0676 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0677 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0678 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A067A Compression Method 0008 (8) 'Deflated' │ │ │ │ +A067C Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0680 CRC FAE30038 (4209180728) │ │ │ │ +A0684 Compressed Size 0000040B (1035) │ │ │ │ +A0688 Uncompressed Size 00000826 (2086) │ │ │ │ +A068C Filename Length 001C (28) │ │ │ │ +A068E Extra Length 0018 (24) │ │ │ │ +A0690 Comment Length 0000 (0) │ │ │ │ +A0692 Disk Start 0000 (0) │ │ │ │ +A0694 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0696 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A069A Local Header Offset 000676E8 (423656) │ │ │ │ +A069E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA069E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A06BA Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A06BC Length 0005 (5) │ │ │ │ +A06BE Flags 01 (1) 'Modification' │ │ │ │ +A06BF Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A06C3 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A06C5 Length 000B (11) │ │ │ │ +A06C7 Version 01 (1) │ │ │ │ +A06C8 UID Size 04 (4) │ │ │ │ +A06C9 UID 00000000 (0) │ │ │ │ +A06CD GID Size 04 (4) │ │ │ │ +A06CE GID 00000000 (0) │ │ │ │ + │ │ │ │ +A06D2 CENTRAL HEADER #57 02014B50 (33639248) │ │ │ │ +A06D6 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A06D7 Created OS 03 (3) 'Unix' │ │ │ │ +A06D8 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A06D9 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A06DA General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A06DC Compression Method 0008 (8) 'Deflated' │ │ │ │ +A06DE Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A06E2 CRC 6F4786FC (1866958588) │ │ │ │ +A06E6 Compressed Size 000024A8 (9384) │ │ │ │ +A06EA Uncompressed Size 0000B5F9 (46585) │ │ │ │ +A06EE Filename Length 001F (31) │ │ │ │ +A06F0 Extra Length 0018 (24) │ │ │ │ +A06F2 Comment Length 0000 (0) │ │ │ │ +A06F4 Disk Start 0000 (0) │ │ │ │ +A06F6 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A06F8 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A06FC Local Header Offset 00067B49 (424777) │ │ │ │ +A0700 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0700: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A071F Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0721 Length 0005 (5) │ │ │ │ +A0723 Flags 01 (1) 'Modification' │ │ │ │ +A0724 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0728 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A072A Length 000B (11) │ │ │ │ +A072C Version 01 (1) │ │ │ │ +A072D UID Size 04 (4) │ │ │ │ +A072E UID 00000000 (0) │ │ │ │ +A0732 GID Size 04 (4) │ │ │ │ +A0733 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0737 CENTRAL HEADER #58 02014B50 (33639248) │ │ │ │ +A073B Created Zip Spec 3D (61) '6.1' │ │ │ │ +A073C Created OS 03 (3) 'Unix' │ │ │ │ +A073D Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A073E Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A073F General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0741 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0743 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0747 CRC 60313637 (1613837879) │ │ │ │ +A074B Compressed Size 00000E5A (3674) │ │ │ │ +A074F Uncompressed Size 0000527E (21118) │ │ │ │ +A0753 Filename Length 001F (31) │ │ │ │ +A0755 Extra Length 0018 (24) │ │ │ │ +A0757 Comment Length 0000 (0) │ │ │ │ +A0759 Disk Start 0000 (0) │ │ │ │ +A075B Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A075D Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0761 Local Header Offset 0006A04A (434250) │ │ │ │ +A0765 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0765: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0784 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0786 Length 0005 (5) │ │ │ │ +A0788 Flags 01 (1) 'Modification' │ │ │ │ +A0789 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A078D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A078F Length 000B (11) │ │ │ │ +A0791 Version 01 (1) │ │ │ │ +A0792 UID Size 04 (4) │ │ │ │ +A0793 UID 00000000 (0) │ │ │ │ +A0797 GID Size 04 (4) │ │ │ │ +A0798 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A079C CENTRAL HEADER #59 02014B50 (33639248) │ │ │ │ +A07A0 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A07A1 Created OS 03 (3) 'Unix' │ │ │ │ +A07A2 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A07A3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A07A4 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A07A6 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A07A8 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A07AC CRC 11754EED (292900589) │ │ │ │ +A07B0 Compressed Size 00000A44 (2628) │ │ │ │ +A07B4 Uncompressed Size 0000244F (9295) │ │ │ │ +A07B8 Filename Length 0013 (19) │ │ │ │ +A07BA Extra Length 0018 (24) │ │ │ │ +A07BC Comment Length 0000 (0) │ │ │ │ +A07BE Disk Start 0000 (0) │ │ │ │ +A07C0 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A07C2 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A07C6 Local Header Offset 0006AEFD (438013) │ │ │ │ +A07CA Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA07CA: Filename 'XXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A07DD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A07DF Length 0005 (5) │ │ │ │ +A07E1 Flags 01 (1) 'Modification' │ │ │ │ +A07E2 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A07E6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A07E8 Length 000B (11) │ │ │ │ +A07EA Version 01 (1) │ │ │ │ +A07EB UID Size 04 (4) │ │ │ │ +A07EC UID 00000000 (0) │ │ │ │ +A07F0 GID Size 04 (4) │ │ │ │ +A07F1 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A07F5 CENTRAL HEADER #60 02014B50 (33639248) │ │ │ │ +A07F9 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A07FA Created OS 03 (3) 'Unix' │ │ │ │ +A07FB Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A07FC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A07FD General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A07FF Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0801 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0805 CRC E5924A27 (3851569703) │ │ │ │ +A0809 Compressed Size 00002487 (9351) │ │ │ │ +A080D Uncompressed Size 0000B84D (47181) │ │ │ │ +A0811 Filename Length 0019 (25) │ │ │ │ +A0813 Extra Length 0018 (24) │ │ │ │ +A0815 Comment Length 0000 (0) │ │ │ │ +A0817 Disk Start 0000 (0) │ │ │ │ +A0819 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A081B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A081F Local Header Offset 0006B98E (440718) │ │ │ │ +A0823 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0823: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A083C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A083E Length 0005 (5) │ │ │ │ +A0840 Flags 01 (1) 'Modification' │ │ │ │ +A0841 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0845 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0847 Length 000B (11) │ │ │ │ +A0849 Version 01 (1) │ │ │ │ +A084A UID Size 04 (4) │ │ │ │ +A084B UID 00000000 (0) │ │ │ │ +A084F GID Size 04 (4) │ │ │ │ +A0850 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0854 CENTRAL HEADER #61 02014B50 (33639248) │ │ │ │ +A0858 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0859 Created OS 03 (3) 'Unix' │ │ │ │ +A085A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A085B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A085C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A085E Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0860 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0864 CRC 31F68DC1 (838241729) │ │ │ │ +A0868 Compressed Size 00000EFC (3836) │ │ │ │ +A086C Uncompressed Size 00003A2D (14893) │ │ │ │ +A0870 Filename Length 0024 (36) │ │ │ │ +A0872 Extra Length 0018 (24) │ │ │ │ +A0874 Comment Length 0000 (0) │ │ │ │ +A0876 Disk Start 0000 (0) │ │ │ │ +A0878 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A087A Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A087E Local Header Offset 0006DE68 (450152) │ │ │ │ +A0882 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0882: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A08A6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A08A8 Length 0005 (5) │ │ │ │ +A08AA Flags 01 (1) 'Modification' │ │ │ │ +A08AB Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A08AF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A08B1 Length 000B (11) │ │ │ │ +A08B3 Version 01 (1) │ │ │ │ +A08B4 UID Size 04 (4) │ │ │ │ +A08B5 UID 00000000 (0) │ │ │ │ +A08B9 GID Size 04 (4) │ │ │ │ +A08BA GID 00000000 (0) │ │ │ │ + │ │ │ │ +A08BE CENTRAL HEADER #62 02014B50 (33639248) │ │ │ │ +A08C2 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A08C3 Created OS 03 (3) 'Unix' │ │ │ │ +A08C4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A08C5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A08C6 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A08C8 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A08CA Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A08CE CRC C6BC95DE (3334247902) │ │ │ │ +A08D2 Compressed Size 00001ABC (6844) │ │ │ │ +A08D6 Uncompressed Size 00005F39 (24377) │ │ │ │ +A08DA Filename Length 0017 (23) │ │ │ │ +A08DC Extra Length 0018 (24) │ │ │ │ +A08DE Comment Length 0000 (0) │ │ │ │ +A08E0 Disk Start 0000 (0) │ │ │ │ +A08E2 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A08E4 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A08E8 Local Header Offset 0006EDC2 (454082) │ │ │ │ +A08EC Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA08EC: Filename 'XXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0903 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0905 Length 0005 (5) │ │ │ │ +A0907 Flags 01 (1) 'Modification' │ │ │ │ +A0908 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A090C Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A090E Length 000B (11) │ │ │ │ +A0910 Version 01 (1) │ │ │ │ +A0911 UID Size 04 (4) │ │ │ │ +A0912 UID 00000000 (0) │ │ │ │ +A0916 GID Size 04 (4) │ │ │ │ +A0917 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A091B CENTRAL HEADER #63 02014B50 (33639248) │ │ │ │ +A091F Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0920 Created OS 03 (3) 'Unix' │ │ │ │ +A0921 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0922 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0923 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0925 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0927 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A092B CRC 3DD8544C (1037587532) │ │ │ │ +A092F Compressed Size 00000ED1 (3793) │ │ │ │ +A0933 Uncompressed Size 000038DE (14558) │ │ │ │ +A0937 Filename Length 0023 (35) │ │ │ │ +A0939 Extra Length 0018 (24) │ │ │ │ +A093B Comment Length 0000 (0) │ │ │ │ +A093D Disk Start 0000 (0) │ │ │ │ +A093F Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0941 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0945 Local Header Offset 000708CF (461007) │ │ │ │ +A0949 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0949: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A096C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A096E Length 0005 (5) │ │ │ │ +A0970 Flags 01 (1) 'Modification' │ │ │ │ +A0971 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0975 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0977 Length 000B (11) │ │ │ │ +A0979 Version 01 (1) │ │ │ │ +A097A UID Size 04 (4) │ │ │ │ +A097B UID 00000000 (0) │ │ │ │ +A097F GID Size 04 (4) │ │ │ │ +A0980 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0984 CENTRAL HEADER #64 02014B50 (33639248) │ │ │ │ +A0988 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0989 Created OS 03 (3) 'Unix' │ │ │ │ +A098A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A098B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A098C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A098E Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0990 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0994 CRC 2DB7929F (767005343) │ │ │ │ +A0998 Compressed Size 00000113 (275) │ │ │ │ +A099C Uncompressed Size 000001F3 (499) │ │ │ │ +A09A0 Filename Length 001B (27) │ │ │ │ +A09A2 Extra Length 0018 (24) │ │ │ │ +A09A4 Comment Length 0000 (0) │ │ │ │ +A09A6 Disk Start 0000 (0) │ │ │ │ +A09A8 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A09AA Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A09AE Local Header Offset 000717FD (464893) │ │ │ │ +A09B2 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA09B2: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A09CD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A09CF Length 0005 (5) │ │ │ │ +A09D1 Flags 01 (1) 'Modification' │ │ │ │ +A09D2 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A09D6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A09D8 Length 000B (11) │ │ │ │ +A09DA Version 01 (1) │ │ │ │ +A09DB UID Size 04 (4) │ │ │ │ +A09DC UID 00000000 (0) │ │ │ │ +A09E0 GID Size 04 (4) │ │ │ │ +A09E1 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A09E5 CENTRAL HEADER #65 02014B50 (33639248) │ │ │ │ +A09E9 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A09EA Created OS 03 (3) 'Unix' │ │ │ │ +A09EB Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A09EC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A09ED General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A09EF Compression Method 0008 (8) 'Deflated' │ │ │ │ +A09F1 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A09F5 CRC D8D75A4A (3637992010) │ │ │ │ +A09F9 Compressed Size 0000189E (6302) │ │ │ │ +A09FD Uncompressed Size 00008F47 (36679) │ │ │ │ +A0A01 Filename Length 001D (29) │ │ │ │ +A0A03 Extra Length 0018 (24) │ │ │ │ +A0A05 Comment Length 0000 (0) │ │ │ │ +A0A07 Disk Start 0000 (0) │ │ │ │ +A0A09 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0A0B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0A0F Local Header Offset 00071965 (465253) │ │ │ │ +A0A13 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0A13: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0A30 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0A32 Length 0005 (5) │ │ │ │ +A0A34 Flags 01 (1) 'Modification' │ │ │ │ +A0A35 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0A39 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0A3B Length 000B (11) │ │ │ │ +A0A3D Version 01 (1) │ │ │ │ +A0A3E UID Size 04 (4) │ │ │ │ +A0A3F UID 00000000 (0) │ │ │ │ +A0A43 GID Size 04 (4) │ │ │ │ +A0A44 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0A48 CENTRAL HEADER #66 02014B50 (33639248) │ │ │ │ +A0A4C Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0A4D Created OS 03 (3) 'Unix' │ │ │ │ +A0A4E Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0A4F Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0A50 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0A52 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0A54 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0A58 CRC 516C0F5D (1366036317) │ │ │ │ +A0A5C Compressed Size 0000164A (5706) │ │ │ │ +A0A60 Uncompressed Size 00003A9C (15004) │ │ │ │ +A0A64 Filename Length 0015 (21) │ │ │ │ +A0A66 Extra Length 0018 (24) │ │ │ │ +A0A68 Comment Length 0000 (0) │ │ │ │ +A0A6A Disk Start 0000 (0) │ │ │ │ +A0A6C Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0A6E Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0A72 Local Header Offset 0007325A (471642) │ │ │ │ +A0A76 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0A76: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0A8B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0A8D Length 0005 (5) │ │ │ │ +A0A8F Flags 01 (1) 'Modification' │ │ │ │ +A0A90 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0A94 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0A96 Length 000B (11) │ │ │ │ +A0A98 Version 01 (1) │ │ │ │ +A0A99 UID Size 04 (4) │ │ │ │ +A0A9A UID 00000000 (0) │ │ │ │ +A0A9E GID Size 04 (4) │ │ │ │ +A0A9F GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0AA3 CENTRAL HEADER #67 02014B50 (33639248) │ │ │ │ +A0AA7 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0AA8 Created OS 03 (3) 'Unix' │ │ │ │ +A0AA9 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0AAA Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0AAB General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0AAD Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0AAF Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0AB3 CRC 0F59A4E7 (257533159) │ │ │ │ +A0AB7 Compressed Size 00003B48 (15176) │ │ │ │ +A0ABB Uncompressed Size 00011C7F (72831) │ │ │ │ +A0ABF Filename Length 0016 (22) │ │ │ │ +A0AC1 Extra Length 0018 (24) │ │ │ │ +A0AC3 Comment Length 0000 (0) │ │ │ │ +A0AC5 Disk Start 0000 (0) │ │ │ │ +A0AC7 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0AC9 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0ACD Local Header Offset 000748F3 (477427) │ │ │ │ +A0AD1 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0AD1: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0AE7 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0AE9 Length 0005 (5) │ │ │ │ +A0AEB Flags 01 (1) 'Modification' │ │ │ │ +A0AEC Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0AF0 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0AF2 Length 000B (11) │ │ │ │ +A0AF4 Version 01 (1) │ │ │ │ +A0AF5 UID Size 04 (4) │ │ │ │ +A0AF6 UID 00000000 (0) │ │ │ │ +A0AFA GID Size 04 (4) │ │ │ │ +A0AFB GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0AFF CENTRAL HEADER #68 02014B50 (33639248) │ │ │ │ +A0B03 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0B04 Created OS 03 (3) 'Unix' │ │ │ │ +A0B05 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0B06 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0B07 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0B09 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0B0B Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0B0F CRC 00851BDD (8723421) │ │ │ │ +A0B13 Compressed Size 00003EF2 (16114) │ │ │ │ +A0B17 Uncompressed Size 0001CBD3 (117715) │ │ │ │ +A0B1B Filename Length 0019 (25) │ │ │ │ +A0B1D Extra Length 0018 (24) │ │ │ │ +A0B1F Comment Length 0000 (0) │ │ │ │ +A0B21 Disk Start 0000 (0) │ │ │ │ +A0B23 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0B25 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0B29 Local Header Offset 0007848B (492683) │ │ │ │ +A0B2D Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0B2D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0B46 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0B48 Length 0005 (5) │ │ │ │ +A0B4A Flags 01 (1) 'Modification' │ │ │ │ +A0B4B Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0B4F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0B51 Length 000B (11) │ │ │ │ +A0B53 Version 01 (1) │ │ │ │ +A0B54 UID Size 04 (4) │ │ │ │ +A0B55 UID 00000000 (0) │ │ │ │ +A0B59 GID Size 04 (4) │ │ │ │ +A0B5A GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0B5E CENTRAL HEADER #69 02014B50 (33639248) │ │ │ │ +A0B62 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0B63 Created OS 03 (3) 'Unix' │ │ │ │ +A0B64 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0B65 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0B66 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0B68 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0B6A Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0B6E CRC 66C2C5FB (1724040699) │ │ │ │ +A0B72 Compressed Size 0000083A (2106) │ │ │ │ +A0B76 Uncompressed Size 00003384 (13188) │ │ │ │ +A0B7A Filename Length 0011 (17) │ │ │ │ +A0B7C Extra Length 0018 (24) │ │ │ │ +A0B7E Comment Length 0000 (0) │ │ │ │ +A0B80 Disk Start 0000 (0) │ │ │ │ +A0B82 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0B84 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0B88 Local Header Offset 0007C3D0 (508880) │ │ │ │ +A0B8C Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0B8C: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0B9D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0B9F Length 0005 (5) │ │ │ │ +A0BA1 Flags 01 (1) 'Modification' │ │ │ │ +A0BA2 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0BA6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0BA8 Length 000B (11) │ │ │ │ +A0BAA Version 01 (1) │ │ │ │ +A0BAB UID Size 04 (4) │ │ │ │ +A0BAC UID 00000000 (0) │ │ │ │ +A0BB0 GID Size 04 (4) │ │ │ │ +A0BB1 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0BB5 CENTRAL HEADER #70 02014B50 (33639248) │ │ │ │ +A0BB9 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0BBA Created OS 03 (3) 'Unix' │ │ │ │ +A0BBB Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0BBC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0BBD General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0BBF Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0BC1 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0BC5 CRC ED0886E6 (3976759014) │ │ │ │ +A0BC9 Compressed Size 000051B2 (20914) │ │ │ │ +A0BCD Uncompressed Size 0001FBE0 (130016) │ │ │ │ +A0BD1 Filename Length 0015 (21) │ │ │ │ +A0BD3 Extra Length 0018 (24) │ │ │ │ +A0BD5 Comment Length 0000 (0) │ │ │ │ +A0BD7 Disk Start 0000 (0) │ │ │ │ +A0BD9 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0BDB Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0BDF Local Header Offset 0007CC55 (511061) │ │ │ │ +A0BE3 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0BE3: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0BF8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0BFA Length 0005 (5) │ │ │ │ +A0BFC Flags 01 (1) 'Modification' │ │ │ │ +A0BFD Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0C01 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0C03 Length 000B (11) │ │ │ │ +A0C05 Version 01 (1) │ │ │ │ +A0C06 UID Size 04 (4) │ │ │ │ +A0C07 UID 00000000 (0) │ │ │ │ +A0C0B GID Size 04 (4) │ │ │ │ +A0C0C GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0C10 CENTRAL HEADER #71 02014B50 (33639248) │ │ │ │ +A0C14 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0C15 Created OS 03 (3) 'Unix' │ │ │ │ +A0C16 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0C17 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0C18 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0C1A Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0C1C Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0C20 CRC EB0AC408 (3943351304) │ │ │ │ +A0C24 Compressed Size 00001B1B (6939) │ │ │ │ +A0C28 Uncompressed Size 00008213 (33299) │ │ │ │ +A0C2C Filename Length 0019 (25) │ │ │ │ +A0C2E Extra Length 0018 (24) │ │ │ │ +A0C30 Comment Length 0000 (0) │ │ │ │ +A0C32 Disk Start 0000 (0) │ │ │ │ +A0C34 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0C36 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0C3A Local Header Offset 00081E56 (532054) │ │ │ │ +A0C3E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0C3E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0C57 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0C59 Length 0005 (5) │ │ │ │ +A0C5B Flags 01 (1) 'Modification' │ │ │ │ +A0C5C Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0C60 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0C62 Length 000B (11) │ │ │ │ +A0C64 Version 01 (1) │ │ │ │ +A0C65 UID Size 04 (4) │ │ │ │ +A0C66 UID 00000000 (0) │ │ │ │ +A0C6A GID Size 04 (4) │ │ │ │ +A0C6B GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0C6F CENTRAL HEADER #72 02014B50 (33639248) │ │ │ │ +A0C73 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0C74 Created OS 03 (3) 'Unix' │ │ │ │ +A0C75 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0C76 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0C77 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0C79 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0C7B Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0C7F CRC 8690A50D (2257626381) │ │ │ │ +A0C83 Compressed Size 00000D96 (3478) │ │ │ │ +A0C87 Uncompressed Size 00002EA0 (11936) │ │ │ │ +A0C8B Filename Length 0018 (24) │ │ │ │ +A0C8D Extra Length 0018 (24) │ │ │ │ +A0C8F Comment Length 0000 (0) │ │ │ │ +A0C91 Disk Start 0000 (0) │ │ │ │ +A0C93 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0C95 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0C99 Local Header Offset 000839C4 (539076) │ │ │ │ +A0C9D Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0C9D: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0CB5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0CB7 Length 0005 (5) │ │ │ │ +A0CB9 Flags 01 (1) 'Modification' │ │ │ │ +A0CBA Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0CBE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0CC0 Length 000B (11) │ │ │ │ +A0CC2 Version 01 (1) │ │ │ │ +A0CC3 UID Size 04 (4) │ │ │ │ +A0CC4 UID 00000000 (0) │ │ │ │ +A0CC8 GID Size 04 (4) │ │ │ │ +A0CC9 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0CCD CENTRAL HEADER #73 02014B50 (33639248) │ │ │ │ +A0CD1 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0CD2 Created OS 03 (3) 'Unix' │ │ │ │ +A0CD3 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0CD4 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0CD5 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0CD7 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0CD9 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0CDD CRC 037E3A9E (58604190) │ │ │ │ +A0CE1 Compressed Size 000001E1 (481) │ │ │ │ +A0CE5 Uncompressed Size 00000324 (804) │ │ │ │ +A0CE9 Filename Length 0011 (17) │ │ │ │ +A0CEB Extra Length 0018 (24) │ │ │ │ +A0CED Comment Length 0000 (0) │ │ │ │ +A0CEF Disk Start 0000 (0) │ │ │ │ +A0CF1 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0CF3 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0CF7 Local Header Offset 000847AC (542636) │ │ │ │ +A0CFB Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0CFB: Filename 'XXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0D0C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0D0E Length 0005 (5) │ │ │ │ +A0D10 Flags 01 (1) 'Modification' │ │ │ │ +A0D11 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0D15 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0D17 Length 000B (11) │ │ │ │ +A0D19 Version 01 (1) │ │ │ │ +A0D1A UID Size 04 (4) │ │ │ │ +A0D1B UID 00000000 (0) │ │ │ │ +A0D1F GID Size 04 (4) │ │ │ │ +A0D20 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0D24 CENTRAL HEADER #74 02014B50 (33639248) │ │ │ │ +A0D28 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0D29 Created OS 03 (3) 'Unix' │ │ │ │ +A0D2A Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0D2B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0D2C General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0D2E Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0D30 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0D34 CRC 55187787 (1427666823) │ │ │ │ +A0D38 Compressed Size 000006C3 (1731) │ │ │ │ +A0D3C Uncompressed Size 0000143A (5178) │ │ │ │ +A0D40 Filename Length 0019 (25) │ │ │ │ +A0D42 Extra Length 0018 (24) │ │ │ │ +A0D44 Comment Length 0000 (0) │ │ │ │ +A0D46 Disk Start 0000 (0) │ │ │ │ +A0D48 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0D4A Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0D4E Local Header Offset 000849D8 (543192) │ │ │ │ +A0D52 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0D52: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0D6B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0D6D Length 0005 (5) │ │ │ │ +A0D6F Flags 01 (1) 'Modification' │ │ │ │ +A0D70 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0D74 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0D76 Length 000B (11) │ │ │ │ +A0D78 Version 01 (1) │ │ │ │ +A0D79 UID Size 04 (4) │ │ │ │ +A0D7A UID 00000000 (0) │ │ │ │ +A0D7E GID Size 04 (4) │ │ │ │ +A0D7F GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0D83 CENTRAL HEADER #75 02014B50 (33639248) │ │ │ │ +A0D87 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0D88 Created OS 03 (3) 'Unix' │ │ │ │ +A0D89 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0D8A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0D8B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0D8D Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0D8F Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0D93 CRC 86D4A529 (2262082857) │ │ │ │ +A0D97 Compressed Size 00001EA6 (7846) │ │ │ │ +A0D9B Uncompressed Size 0000CA55 (51797) │ │ │ │ +A0D9F Filename Length 0018 (24) │ │ │ │ +A0DA1 Extra Length 0018 (24) │ │ │ │ +A0DA3 Comment Length 0000 (0) │ │ │ │ +A0DA5 Disk Start 0000 (0) │ │ │ │ +A0DA7 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0DA9 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0DAD Local Header Offset 000850EE (545006) │ │ │ │ +A0DB1 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0DB1: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0DC9 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0DCB Length 0005 (5) │ │ │ │ +A0DCD Flags 01 (1) 'Modification' │ │ │ │ +A0DCE Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0DD2 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0DD4 Length 000B (11) │ │ │ │ +A0DD6 Version 01 (1) │ │ │ │ +A0DD7 UID Size 04 (4) │ │ │ │ +A0DD8 UID 00000000 (0) │ │ │ │ +A0DDC GID Size 04 (4) │ │ │ │ +A0DDD GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0DE1 CENTRAL HEADER #76 02014B50 (33639248) │ │ │ │ +A0DE5 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0DE6 Created OS 03 (3) 'Unix' │ │ │ │ +A0DE7 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0DE8 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0DE9 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0DEB Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0DED Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0DF1 CRC 077CFA4B (125631051) │ │ │ │ +A0DF5 Compressed Size 00001C07 (7175) │ │ │ │ +A0DF9 Uncompressed Size 0000C5BE (50622) │ │ │ │ +A0DFD Filename Length 0012 (18) │ │ │ │ +A0DFF Extra Length 0018 (24) │ │ │ │ +A0E01 Comment Length 0000 (0) │ │ │ │ +A0E03 Disk Start 0000 (0) │ │ │ │ +A0E05 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0E07 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0E0B Local Header Offset 00086FE6 (552934) │ │ │ │ +A0E0F Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0E0F: Filename 'XXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0E21 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0E23 Length 0005 (5) │ │ │ │ +A0E25 Flags 01 (1) 'Modification' │ │ │ │ +A0E26 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0E2A Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0E2C Length 000B (11) │ │ │ │ +A0E2E Version 01 (1) │ │ │ │ +A0E2F UID Size 04 (4) │ │ │ │ +A0E30 UID 00000000 (0) │ │ │ │ +A0E34 GID Size 04 (4) │ │ │ │ +A0E35 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0E39 CENTRAL HEADER #77 02014B50 (33639248) │ │ │ │ +A0E3D Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0E3E Created OS 03 (3) 'Unix' │ │ │ │ +A0E3F Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0E40 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0E41 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0E43 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0E45 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0E49 CRC 72524D81 (1917996417) │ │ │ │ +A0E4D Compressed Size 00001E16 (7702) │ │ │ │ +A0E51 Uncompressed Size 000087D8 (34776) │ │ │ │ +A0E55 Filename Length 0016 (22) │ │ │ │ +A0E57 Extra Length 0018 (24) │ │ │ │ +A0E59 Comment Length 0000 (0) │ │ │ │ +A0E5B Disk Start 0000 (0) │ │ │ │ +A0E5D Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0E5F Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0E63 Local Header Offset 00088C39 (560185) │ │ │ │ +A0E67 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0E67: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0E7D Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0E7F Length 0005 (5) │ │ │ │ +A0E81 Flags 01 (1) 'Modification' │ │ │ │ +A0E82 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0E86 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0E88 Length 000B (11) │ │ │ │ +A0E8A Version 01 (1) │ │ │ │ +A0E8B UID Size 04 (4) │ │ │ │ +A0E8C UID 00000000 (0) │ │ │ │ +A0E90 GID Size 04 (4) │ │ │ │ +A0E91 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0E95 CENTRAL HEADER #78 02014B50 (33639248) │ │ │ │ +A0E99 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0E9A Created OS 03 (3) 'Unix' │ │ │ │ +A0E9B Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0E9C Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0E9D General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0E9F Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0EA1 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0EA5 CRC 4A3E1EE6 (1245585126) │ │ │ │ +A0EA9 Compressed Size 000029AC (10668) │ │ │ │ +A0EAD Uncompressed Size 0000D039 (53305) │ │ │ │ +A0EB1 Filename Length 001A (26) │ │ │ │ +A0EB3 Extra Length 0018 (24) │ │ │ │ +A0EB5 Comment Length 0000 (0) │ │ │ │ +A0EB7 Disk Start 0000 (0) │ │ │ │ +A0EB9 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0EBB Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0EBF Local Header Offset 0008AA9F (567967) │ │ │ │ +A0EC3 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0EC3: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0EDD Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0EDF Length 0005 (5) │ │ │ │ +A0EE1 Flags 01 (1) 'Modification' │ │ │ │ +A0EE2 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0EE6 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0EE8 Length 000B (11) │ │ │ │ +A0EEA Version 01 (1) │ │ │ │ +A0EEB UID Size 04 (4) │ │ │ │ +A0EEC UID 00000000 (0) │ │ │ │ +A0EF0 GID Size 04 (4) │ │ │ │ +A0EF1 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0EF5 CENTRAL HEADER #79 02014B50 (33639248) │ │ │ │ +A0EF9 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0EFA Created OS 03 (3) 'Unix' │ │ │ │ +A0EFB Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0EFC Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0EFD General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0EFF Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0F01 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0F05 CRC C2361EE0 (3258326752) │ │ │ │ +A0F09 Compressed Size 000009AE (2478) │ │ │ │ +A0F0D Uncompressed Size 00001DB7 (7607) │ │ │ │ +A0F11 Filename Length 0018 (24) │ │ │ │ +A0F13 Extra Length 0018 (24) │ │ │ │ +A0F15 Comment Length 0000 (0) │ │ │ │ +A0F17 Disk Start 0000 (0) │ │ │ │ +A0F19 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0F1B Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0F1F Local Header Offset 0008D49F (578719) │ │ │ │ +A0F23 Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0F23: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0F3B Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0F3D Length 0005 (5) │ │ │ │ +A0F3F Flags 01 (1) 'Modification' │ │ │ │ +A0F40 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0F44 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0F46 Length 000B (11) │ │ │ │ +A0F48 Version 01 (1) │ │ │ │ +A0F49 UID Size 04 (4) │ │ │ │ +A0F4A UID 00000000 (0) │ │ │ │ +A0F4E GID Size 04 (4) │ │ │ │ +A0F4F GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0F53 CENTRAL HEADER #80 02014B50 (33639248) │ │ │ │ +A0F57 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0F58 Created OS 03 (3) 'Unix' │ │ │ │ +A0F59 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0F5A Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0F5B General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0F5D Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0F5F Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0F63 CRC F5E2129F (4125233823) │ │ │ │ +A0F67 Compressed Size 000016BC (5820) │ │ │ │ +A0F6B Uncompressed Size 000016CD (5837) │ │ │ │ +A0F6F Filename Length 0015 (21) │ │ │ │ +A0F71 Extra Length 0018 (24) │ │ │ │ +A0F73 Comment Length 0000 (0) │ │ │ │ +A0F75 Disk Start 0000 (0) │ │ │ │ +A0F77 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0F79 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0F7D Local Header Offset 0008DE9F (581279) │ │ │ │ +A0F81 Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0F81: Filename 'XXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0F96 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0F98 Length 0005 (5) │ │ │ │ +A0F9A Flags 01 (1) 'Modification' │ │ │ │ +A0F9B Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0F9F Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A0FA1 Length 000B (11) │ │ │ │ +A0FA3 Version 01 (1) │ │ │ │ +A0FA4 UID Size 04 (4) │ │ │ │ +A0FA5 UID 00000000 (0) │ │ │ │ +A0FA9 GID Size 04 (4) │ │ │ │ +A0FAA GID 00000000 (0) │ │ │ │ + │ │ │ │ +A0FAE CENTRAL HEADER #81 02014B50 (33639248) │ │ │ │ +A0FB2 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A0FB3 Created OS 03 (3) 'Unix' │ │ │ │ +A0FB4 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A0FB5 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A0FB6 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A0FB8 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A0FBA Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A0FBE CRC F5E2129F (4125233823) │ │ │ │ +A0FC2 Compressed Size 000016BC (5820) │ │ │ │ +A0FC6 Uncompressed Size 000016CD (5837) │ │ │ │ +A0FCA Filename Length 001C (28) │ │ │ │ +A0FCC Extra Length 0018 (24) │ │ │ │ +A0FCE Comment Length 0000 (0) │ │ │ │ +A0FD0 Disk Start 0000 (0) │ │ │ │ +A0FD2 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A0FD4 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A0FD8 Local Header Offset 0008F5AA (587178) │ │ │ │ +A0FDC Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA0FDC: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A0FF8 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A0FFA Length 0005 (5) │ │ │ │ +A0FFC Flags 01 (1) 'Modification' │ │ │ │ +A0FFD Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A1001 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A1003 Length 000B (11) │ │ │ │ +A1005 Version 01 (1) │ │ │ │ +A1006 UID Size 04 (4) │ │ │ │ +A1007 UID 00000000 (0) │ │ │ │ +A100B GID Size 04 (4) │ │ │ │ +A100C GID 00000000 (0) │ │ │ │ + │ │ │ │ +A1010 CENTRAL HEADER #82 02014B50 (33639248) │ │ │ │ +A1014 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A1015 Created OS 03 (3) 'Unix' │ │ │ │ +A1016 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +A1017 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A1018 General Purpose Flag 0000 (0) │ │ │ │ +A101A Compression Method 0000 (0) 'Stored' │ │ │ │ +A101C Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A1020 CRC FC95F24B (4237685323) │ │ │ │ +A1024 Compressed Size 00001B84 (7044) │ │ │ │ +A1028 Uncompressed Size 00001B84 (7044) │ │ │ │ +A102C Filename Length 0016 (22) │ │ │ │ +A102E Extra Length 0018 (24) │ │ │ │ +A1030 Comment Length 0000 (0) │ │ │ │ +A1032 Disk Start 0000 (0) │ │ │ │ +A1034 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A1036 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A103A Local Header Offset 00090CBC (593084) │ │ │ │ +A103E Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA103E: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A1054 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A1056 Length 0005 (5) │ │ │ │ +A1058 Flags 01 (1) 'Modification' │ │ │ │ +A1059 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A105D Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A105F Length 000B (11) │ │ │ │ +A1061 Version 01 (1) │ │ │ │ +A1062 UID Size 04 (4) │ │ │ │ +A1063 UID 00000000 (0) │ │ │ │ +A1067 GID Size 04 (4) │ │ │ │ +A1068 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A106C CENTRAL HEADER #83 02014B50 (33639248) │ │ │ │ +A1070 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A1071 Created OS 03 (3) 'Unix' │ │ │ │ +A1072 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +A1073 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A1074 General Purpose Flag 0000 (0) │ │ │ │ +A1076 Compression Method 0000 (0) 'Stored' │ │ │ │ +A1078 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A107C CRC D0D71F86 (3503759238) │ │ │ │ +A1080 Compressed Size 00000B7B (2939) │ │ │ │ +A1084 Uncompressed Size 00000B7B (2939) │ │ │ │ +A1088 Filename Length 0016 (22) │ │ │ │ +A108A Extra Length 0018 (24) │ │ │ │ +A108C Comment Length 0000 (0) │ │ │ │ +A108E Disk Start 0000 (0) │ │ │ │ +A1090 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A1092 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A1096 Local Header Offset 00092890 (600208) │ │ │ │ +A109A Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA109A: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A10B0 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A10B2 Length 0005 (5) │ │ │ │ +A10B4 Flags 01 (1) 'Modification' │ │ │ │ +A10B5 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A10B9 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A10BB Length 000B (11) │ │ │ │ +A10BD Version 01 (1) │ │ │ │ +A10BE UID Size 04 (4) │ │ │ │ +A10BF UID 00000000 (0) │ │ │ │ +A10C3 GID Size 04 (4) │ │ │ │ +A10C4 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A10C8 CENTRAL HEADER #84 02014B50 (33639248) │ │ │ │ +A10CC Created Zip Spec 3D (61) '6.1' │ │ │ │ +A10CD Created OS 03 (3) 'Unix' │ │ │ │ +A10CE Extract Zip Spec 0A (10) '1.0' │ │ │ │ +A10CF Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A10D0 General Purpose Flag 0000 (0) │ │ │ │ +A10D2 Compression Method 0000 (0) 'Stored' │ │ │ │ +A10D4 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A10D8 CRC FFF9C4D2 (4294558930) │ │ │ │ +A10DC Compressed Size 0000138F (5007) │ │ │ │ +A10E0 Uncompressed Size 0000138F (5007) │ │ │ │ +A10E4 Filename Length 0016 (22) │ │ │ │ +A10E6 Extra Length 0018 (24) │ │ │ │ +A10E8 Comment Length 0000 (0) │ │ │ │ +A10EA Disk Start 0000 (0) │ │ │ │ +A10EC Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A10EE Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A10F2 Local Header Offset 0009345B (603227) │ │ │ │ +A10F6 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA10F6: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A110C Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A110E Length 0005 (5) │ │ │ │ +A1110 Flags 01 (1) 'Modification' │ │ │ │ +A1111 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A1115 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A1117 Length 000B (11) │ │ │ │ +A1119 Version 01 (1) │ │ │ │ +A111A UID Size 04 (4) │ │ │ │ +A111B UID 00000000 (0) │ │ │ │ +A111F GID Size 04 (4) │ │ │ │ +A1120 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A1124 CENTRAL HEADER #85 02014B50 (33639248) │ │ │ │ +A1128 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A1129 Created OS 03 (3) 'Unix' │ │ │ │ +A112A Extract Zip Spec 0A (10) '1.0' │ │ │ │ +A112B Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A112C General Purpose Flag 0000 (0) │ │ │ │ +A112E Compression Method 0000 (0) 'Stored' │ │ │ │ +A1130 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A1134 CRC A1037E8E (2701360782) │ │ │ │ +A1138 Compressed Size 0000145E (5214) │ │ │ │ +A113C Uncompressed Size 0000145E (5214) │ │ │ │ +A1140 Filename Length 0016 (22) │ │ │ │ +A1142 Extra Length 0018 (24) │ │ │ │ +A1144 Comment Length 0000 (0) │ │ │ │ +A1146 Disk Start 0000 (0) │ │ │ │ +A1148 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A114A Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A114E Local Header Offset 0009483A (608314) │ │ │ │ +A1152 Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA1152: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A1168 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A116A Length 0005 (5) │ │ │ │ +A116C Flags 01 (1) 'Modification' │ │ │ │ +A116D Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A1171 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A1173 Length 000B (11) │ │ │ │ +A1175 Version 01 (1) │ │ │ │ +A1176 UID Size 04 (4) │ │ │ │ +A1177 UID 00000000 (0) │ │ │ │ +A117B GID Size 04 (4) │ │ │ │ +A117C GID 00000000 (0) │ │ │ │ + │ │ │ │ +A1180 CENTRAL HEADER #86 02014B50 (33639248) │ │ │ │ +A1184 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A1185 Created OS 03 (3) 'Unix' │ │ │ │ +A1186 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +A1187 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A1188 General Purpose Flag 0000 (0) │ │ │ │ +A118A Compression Method 0000 (0) 'Stored' │ │ │ │ +A118C Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A1190 CRC 5E9E64F1 (1587438833) │ │ │ │ +A1194 Compressed Size 000008EC (2284) │ │ │ │ +A1198 Uncompressed Size 000008EC (2284) │ │ │ │ +A119C Filename Length 0016 (22) │ │ │ │ +A119E Extra Length 0018 (24) │ │ │ │ +A11A0 Comment Length 0000 (0) │ │ │ │ +A11A2 Disk Start 0000 (0) │ │ │ │ +A11A4 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A11A6 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A11AA Local Header Offset 00095CE8 (613608) │ │ │ │ +A11AE Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA11AE: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A11C4 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A11C6 Length 0005 (5) │ │ │ │ +A11C8 Flags 01 (1) 'Modification' │ │ │ │ +A11C9 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A11CD Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A11CF Length 000B (11) │ │ │ │ +A11D1 Version 01 (1) │ │ │ │ +A11D2 UID Size 04 (4) │ │ │ │ +A11D3 UID 00000000 (0) │ │ │ │ +A11D7 GID Size 04 (4) │ │ │ │ +A11D8 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A11DC CENTRAL HEADER #87 02014B50 (33639248) │ │ │ │ +A11E0 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A11E1 Created OS 03 (3) 'Unix' │ │ │ │ +A11E2 Extract Zip Spec 0A (10) '1.0' │ │ │ │ +A11E3 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A11E4 General Purpose Flag 0000 (0) │ │ │ │ +A11E6 Compression Method 0000 (0) 'Stored' │ │ │ │ +A11E8 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A11EC CRC 42E340AB (1122189483) │ │ │ │ +A11F0 Compressed Size 00001F2E (7982) │ │ │ │ +A11F4 Uncompressed Size 00001F2E (7982) │ │ │ │ +A11F8 Filename Length 001E (30) │ │ │ │ +A11FA Extra Length 0018 (24) │ │ │ │ +A11FC Comment Length 0000 (0) │ │ │ │ +A11FE Disk Start 0000 (0) │ │ │ │ +A1200 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A1202 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A1206 Local Header Offset 00096624 (615972) │ │ │ │ +A120A Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA120A: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A1228 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A122A Length 0005 (5) │ │ │ │ +A122C Flags 01 (1) 'Modification' │ │ │ │ +A122D Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A1231 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A1233 Length 000B (11) │ │ │ │ +A1235 Version 01 (1) │ │ │ │ +A1236 UID Size 04 (4) │ │ │ │ +A1237 UID 00000000 (0) │ │ │ │ +A123B GID Size 04 (4) │ │ │ │ +A123C GID 00000000 (0) │ │ │ │ + │ │ │ │ +A1240 CENTRAL HEADER #88 02014B50 (33639248) │ │ │ │ +A1244 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A1245 Created OS 03 (3) 'Unix' │ │ │ │ +A1246 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A1247 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A1248 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A124A Compression Method 0008 (8) 'Deflated' │ │ │ │ +A124C Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A1250 CRC 652B4598 (1697334680) │ │ │ │ +A1254 Compressed Size 000040A1 (16545) │ │ │ │ +A1258 Uncompressed Size 0001964C (104012) │ │ │ │ +A125C Filename Length 001A (26) │ │ │ │ +A125E Extra Length 0018 (24) │ │ │ │ +A1260 Comment Length 0000 (0) │ │ │ │ +A1262 Disk Start 0000 (0) │ │ │ │ +A1264 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A1266 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A126A Local Header Offset 000985AA (624042) │ │ │ │ +A126E Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA126E: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A1288 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A128A Length 0005 (5) │ │ │ │ +A128C Flags 01 (1) 'Modification' │ │ │ │ +A128D Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A1291 Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A1293 Length 000B (11) │ │ │ │ +A1295 Version 01 (1) │ │ │ │ +A1296 UID Size 04 (4) │ │ │ │ +A1297 UID 00000000 (0) │ │ │ │ +A129B GID Size 04 (4) │ │ │ │ +A129C GID 00000000 (0) │ │ │ │ + │ │ │ │ +A12A0 CENTRAL HEADER #89 02014B50 (33639248) │ │ │ │ +A12A4 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A12A5 Created OS 03 (3) 'Unix' │ │ │ │ +A12A6 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A12A7 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A12A8 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A12AA Compression Method 0008 (8) 'Deflated' │ │ │ │ +A12AC Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A12B0 CRC 54E1DD8B (1424088459) │ │ │ │ +A12B4 Compressed Size 000029CB (10699) │ │ │ │ +A12B8 Uncompressed Size 0000BAF5 (47861) │ │ │ │ +A12BC Filename Length 0018 (24) │ │ │ │ +A12BE Extra Length 0018 (24) │ │ │ │ +A12C0 Comment Length 0000 (0) │ │ │ │ +A12C2 Disk Start 0000 (0) │ │ │ │ +A12C4 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A12C6 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A12CA Local Header Offset 0009C69F (640671) │ │ │ │ +A12CE Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA12CE: Filename 'XXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A12E6 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A12E8 Length 0005 (5) │ │ │ │ +A12EA Flags 01 (1) 'Modification' │ │ │ │ +A12EB Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A12EF Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A12F1 Length 000B (11) │ │ │ │ +A12F3 Version 01 (1) │ │ │ │ +A12F4 UID Size 04 (4) │ │ │ │ +A12F5 UID 00000000 (0) │ │ │ │ +A12F9 GID Size 04 (4) │ │ │ │ +A12FA GID 00000000 (0) │ │ │ │ + │ │ │ │ +A12FE CENTRAL HEADER #90 02014B50 (33639248) │ │ │ │ +A1302 Created Zip Spec 3D (61) '6.1' │ │ │ │ +A1303 Created OS 03 (3) 'Unix' │ │ │ │ +A1304 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A1305 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A1306 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A1308 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A130A Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A130E CRC DCB3B516 (3702764822) │ │ │ │ +A1312 Compressed Size 000000AE (174) │ │ │ │ +A1316 Uncompressed Size 000000FC (252) │ │ │ │ +A131A Filename Length 0016 (22) │ │ │ │ +A131C Extra Length 0018 (24) │ │ │ │ +A131E Comment Length 0000 (0) │ │ │ │ +A1320 Disk Start 0000 (0) │ │ │ │ +A1322 Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A1324 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A1328 Local Header Offset 0009F0BC (651452) │ │ │ │ +A132C Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA132C: Filename 'XXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A1342 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A1344 Length 0005 (5) │ │ │ │ +A1346 Flags 01 (1) 'Modification' │ │ │ │ +A1347 Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A134B Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A134D Length 000B (11) │ │ │ │ +A134F Version 01 (1) │ │ │ │ +A1350 UID Size 04 (4) │ │ │ │ +A1351 UID 00000000 (0) │ │ │ │ +A1355 GID Size 04 (4) │ │ │ │ +A1356 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A135A CENTRAL HEADER #91 02014B50 (33639248) │ │ │ │ +A135E Created Zip Spec 3D (61) '6.1' │ │ │ │ +A135F Created OS 03 (3) 'Unix' │ │ │ │ +A1360 Extract Zip Spec 14 (20) '2.0' │ │ │ │ +A1361 Extract OS 00 (0) 'MS-DOS' │ │ │ │ +A1362 General Purpose Flag 0000 (0) │ │ │ │ + [Bits 1-2] 0 'Normal Compression' │ │ │ │ +A1364 Compression Method 0008 (8) 'Deflated' │ │ │ │ +A1366 Modification Time 5CB17C21 (1555135521) 'Sun May 17 15:33:02 2026' │ │ │ │ +A136A CRC 58439733 (1480824627) │ │ │ │ +A136E Compressed Size 00000077 (119) │ │ │ │ +A1372 Uncompressed Size 000000A2 (162) │ │ │ │ +A1376 Filename Length 002D (45) │ │ │ │ +A1378 Extra Length 0018 (24) │ │ │ │ +A137A Comment Length 0000 (0) │ │ │ │ +A137C Disk Start 0000 (0) │ │ │ │ +A137E Int File Attributes 0000 (0) │ │ │ │ + [Bit 0] 0 'Binary Data' │ │ │ │ +A1380 Ext File Attributes 01A40000 (27525120) │ │ │ │ + [Bits 16-24] 01A4 (420) 'Unix attrib: rw-r--r--' │ │ │ │ +A1384 Local Header Offset 0009F1BA (651706) │ │ │ │ +A1388 Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# │ │ │ │ +# WARNING: Offset 0xA1388: Filename 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX' │ │ │ │ +# Zero length filename │ │ │ │ +# │ │ │ │ +A13B5 Extra ID #1 5455 (21589) 'Extended Timestamp [UT]' │ │ │ │ +A13B7 Length 0005 (5) │ │ │ │ +A13B9 Flags 01 (1) 'Modification' │ │ │ │ +A13BA Modification Time 6A09DFAE (1779031982) 'Sun May 17 15:33:02 2026' │ │ │ │ +A13BE Extra ID #2 7875 (30837) 'Unix Extra type 3 [ux]' │ │ │ │ +A13C0 Length 000B (11) │ │ │ │ +A13C2 Version 01 (1) │ │ │ │ +A13C3 UID Size 04 (4) │ │ │ │ +A13C4 UID 00000000 (0) │ │ │ │ +A13C8 GID Size 04 (4) │ │ │ │ +A13C9 GID 00000000 (0) │ │ │ │ + │ │ │ │ +A13CD END CENTRAL HEADER 06054B50 (101010256) │ │ │ │ +A13D1 Number of this disk 0000 (0) │ │ │ │ +A13D3 Central Dir Disk no 0000 (0) │ │ │ │ +A13D5 Entries in this disk 005B (91) │ │ │ │ +A13D7 Total Entries 005B (91) │ │ │ │ +A13D9 Size of Central Dir 00002135 (8501) │ │ │ │ +A13DD Offset to Central Dir 0009F298 (651928) │ │ │ │ +A13E1 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 17 2026 13:22:22, uncompressed size 20, method=store │ │ │ │ +Zip archive data, made by v6.1 UNIX, extract using at least v1.0, last modified May 17 2026 15:33:02, 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:f63264b8-0550-9027-197e-26ac74700159 │ │ │ │ │ + urn:uuid:f4914638-669e-a10d-3dc4-5465534bb8f7 │ │ │ │ │ en │ │ │ │ │ - 2026-05-17T13:22:22Z │ │ │ │ │ + 2026-05-17T15:33:02Z │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ │ @@ -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 │ │ │ @@ -234,15 +234,15 @@ │ │ │ %% │ │ │ %% In OTP 27 it is instead interpreted as a │ │ │ %% Triple-Quoted String equivalent to │ │ │ "String Content"

        """"
        │ │ │  ++ 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/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; col < n_cols; ++col) {
    │ │ │ -                ei_x_encode_string(x, PQgetvalue(res, row, col));
    │ │ │ -            }
    │ │ │ -            ei_x_encode_empty_list(x);
    │ │ │ -        }
    │ │ │ -        ei_x_encode_empty_list(x);
    │ │ │ +        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